pax_global_header00006660000000000000000000000064151543034440014515gustar00rootroot0000000000000052 comment=d88f5d96087d4daf40220206c09ca60116aed99b lemonade-sdk-lemonade-d88f5d9/000077500000000000000000000000001515430344400162775ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.clangd000066400000000000000000000001521515430344400175260ustar00rootroot00000000000000CompileFlags: CompilationDatabase: build Add: [-Wall, -Wextra, -Wpedantic] Index: Background: Build lemonade-sdk-lemonade-d88f5d9/.devcontainer/000077500000000000000000000000001515430344400210365ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.devcontainer/Dockerfile000066400000000000000000000015041515430344400230300ustar00rootroot00000000000000FROM mcr.microsoft.com/devcontainers/cpp:1-debian-12 ARG REINSTALL_CMAKE_VERSION_FROM_SOURCE="3.22.2" COPY .devcontainer/reinstall-cmake.sh /tmp/ RUN if [ "${REINSTALL_CMAKE_VERSION_FROM_SOURCE}" != "none" ]; then \ chmod +x /tmp/reinstall-cmake.sh && /tmp/reinstall-cmake.sh ${REINSTALL_CMAKE_VERSION_FROM_SOURCE}; \ fi \ && rm -f /tmp/reinstall-cmake.sh # Install Dependencies RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends \ python3 \ python3-pip \ python3-dev \ python3-venv \ python3-setuptools \ build-essential \ libdrm-dev \ git # Setup Venv ENV VIRTUAL_ENV=/opt/venv RUN python3 -m venv $VIRTUAL_ENV ENV PATH="$VIRTUAL_ENV/bin:$PATH" WORKDIR /usr/src/app # Copy the Project Root into the container COPY . . lemonade-sdk-lemonade-d88f5d9/.devcontainer/devcontainer.json000066400000000000000000000022601515430344400244120ustar00rootroot00000000000000// For format details, see https://aka.ms/devcontainer.json. For config options, see the // README at: https://github.com/devcontainers/templates/tree/main/src/cpp { "name": "C++", "build": { "dockerfile": "Dockerfile", "context": ".." }, "features": { "ghcr.io/devcontainers/features/python:1": {} }, "customizations": { "vscode": { "extensions": [ "ms-vscode.cmake-tools", "ms-python.python", // Official Python Extension "ms-python.vscode-pylance", // (Recommended) Better IntelliSense for Python "ms-vscode.cpptools", // Official C/C++ Extension "ms-vscode.cpptools-extension-pack" // (Optional) Adds CMake & Themes ] } } // Features to add to the dev container. More info: https://containers.dev/features. // "features": {}, // Use 'forwardPorts' to make a list of ports inside the container available locally. // "forwardPorts": [], // Use 'postCreateCommand' to run commands after the container is created. // "postCreateCommand": "gcc -v", // Configure tool-specific properties. // "customizations": {}, // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" } lemonade-sdk-lemonade-d88f5d9/.devcontainer/reinstall-cmake.sh000066400000000000000000000034071515430344400244510ustar00rootroot00000000000000#!/usr/bin/env bash #------------------------------------------------------------------------------------------------------------- # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. #------------------------------------------------------------------------------------------------------------- # set -e CMAKE_VERSION=${1:-"none"} if [ "${CMAKE_VERSION}" = "none" ]; then echo "No CMake version specified, skipping CMake reinstallation" exit 0 fi # Cleanup temporary directory and associated files when exiting the script. cleanup() { EXIT_CODE=$? set +e if [[ -n "${TMP_DIR}" ]]; then echo "Executing cleanup of tmp files" rm -Rf "${TMP_DIR}" fi exit $EXIT_CODE } trap cleanup EXIT echo "Installing CMake..." apt-get -y purge --auto-remove cmake mkdir -p /opt/cmake architecture=$(dpkg --print-architecture) case "${architecture}" in arm64) ARCH=aarch64 ;; amd64) ARCH=x86_64 ;; *) echo "Unsupported architecture ${architecture}." exit 1 ;; esac CMAKE_BINARY_NAME="cmake-${CMAKE_VERSION}-linux-${ARCH}.sh" CMAKE_CHECKSUM_NAME="cmake-${CMAKE_VERSION}-SHA-256.txt" TMP_DIR=$(mktemp -d -t cmake-XXXXXXXXXX) echo "${TMP_DIR}" cd "${TMP_DIR}" curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_BINARY_NAME}" -O curl -sSL "https://github.com/Kitware/CMake/releases/download/v${CMAKE_VERSION}/${CMAKE_CHECKSUM_NAME}" -O sha256sum -c --ignore-missing "${CMAKE_CHECKSUM_NAME}" sh "${TMP_DIR}/${CMAKE_BINARY_NAME}" --prefix=/opt/cmake --skip-license ln -s /opt/cmake/bin/cmake /usr/local/bin/cmake ln -s /opt/cmake/bin/ctest /usr/local/bin/ctest lemonade-sdk-lemonade-d88f5d9/.dockerignore000066400000000000000000000004451515430344400207560ustar00rootroot00000000000000# Git / repo metadata .git .github .gitattributes .gitignore .vscode # Dev & CI tooling .devcontainer .pylintrc # Docs docs mkdocs.yml README.md NOTICE.md LICENSE # Tests and Examples test examples # Python packaging (not used in image) pyproject.toml setup.py **/__pycache__ # Misc *.md lemonade-sdk-lemonade-d88f5d9/.gitattributes000066400000000000000000000000211515430344400211630ustar00rootroot00000000000000*.py text eol=lf lemonade-sdk-lemonade-d88f5d9/.github/000077500000000000000000000000001515430344400176375ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/000077500000000000000000000000001515430344400212775ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/build-linux-deb/000077500000000000000000000000001515430344400242635ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/build-linux-deb/action.yml000066400000000000000000000042131515430344400262630ustar00rootroot00000000000000name: 'Build Linux .deb Package' description: 'Build Lemonade .deb package for Linux' outputs: package-name: description: 'The name of the generated .deb package file' value: ${{ steps.build-deb.outputs.package_name }} runs: using: "composite" steps: - name: Run setup.sh to configure environment shell: bash run: | set -e echo "Running setup.sh to configure build environment..." bash setup.sh echo "Build environment configured successfully!" - name: Build C++ Server with CMake shell: bash run: | set -e echo "Building lemonade-router and lemonade-server for Linux..." # Build (BUILD_ELECTRON_APP will trigger electron build automatically) echo "Building binaries..." cmake --build --preset default # Verify binaries exist if [ ! -f "build/lemonade-router" ]; then echo "ERROR: lemonade-router not found!" echo "Build directory contents:" ls -lh build/ exit 1 fi if [ ! -f "build/lemonade-server" ]; then echo "ERROR: lemonade-server not found!" echo "Build directory contents:" ls -lh build/ exit 1 fi echo "✓ Binaries found successfully" echo "Verifying binary executability..." ./build/lemonade-router --version ./build/lemonade-server --version echo "C++ build successful!" - name: Build .deb package id: build-deb shell: bash run: | set -e DEB_FILE="lemonade-server_${LEMONADE_VERSION}_amd64.deb" cd build echo "Creating .deb package with CPack..." cpack -G DEB -V echo "Looking for: $DEB_FILE" if [ ! -f "$DEB_FILE" ]; then echo "ERROR: .deb package not created!" echo "Contents of build directory:" ls -lR . exit 1 fi # Show package info echo "Package information:" dpkg-deb --info "$DEB_FILE" # Output the package name for subsequent steps echo "package_name=$DEB_FILE" >> $GITHUB_OUTPUT lemonade-sdk-lemonade-d88f5d9/.github/actions/build-macos-dmg/000077500000000000000000000000001515430344400242435ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/build-macos-dmg/action.yml000066400000000000000000000146211515430344400262470ustar00rootroot00000000000000name: 'Build macOS .dmg Package' description: 'Build Lemonade .dmg for macOS (signed and notarized via CMake notarize target)' inputs: include-electron: description: 'Whether to include the Electron app (full build)' required: false default: 'true' skip-packaging: description: 'Skip .pkg packaging and notarization (for unsigned local builds)' required: false default: 'false' outputs: artifact-name: description: 'The name of the generated .dmg file' value: ${{ steps.build-dmg.outputs.artifact_name }} runs: using: "composite" steps: - name: Run setup.sh to configure environment shell: bash run: | set -e echo "Running setup.sh to configure build environment..." bash setup.sh echo "Build environment configured successfully!" - name: Setup Node.js if: inputs.include-electron == 'true' uses: actions/setup-node@v4 with: node-version: '20' - name: Set Electron signing identity shell: bash run: | # Tell electron-builder to use the Developer ID Application cert from keychain if [ -n "$DEVELOPER_ID_APPLICATION_IDENTITY" ]; then echo "CSC_NAME=$DEVELOPER_ID_APPLICATION_IDENTITY" >> $GITHUB_ENV # electron-builder skips signing on PR branches by default echo "CSC_FOR_PULL_REQUEST=true" >> $GITHUB_ENV echo "Electron builder will sign with: $DEVELOPER_ID_APPLICATION_IDENTITY" else echo "WARNING: No signing identity found, electron-builder will not sign" fi - name: Build C++ Server with CMake shell: bash run: | set -e echo "Building lemonade-router and lemonade-server for macOS..." # Reconfigure if Electron app is included (setup.sh already did basic config) EXTRA_CMAKE_ARGS="" if [ "${{ inputs.skip-packaging }}" == "true" ]; then echo "No signing identity available — using ad-hoc signing" EXTRA_CMAKE_ARGS="-DSIGNING_IDENTITY=- -DINSTALLER_IDENTITY=-" fi if [ "${{ inputs.include-electron }}" == "true" ]; then echo "Reconfiguring CMake with Electron app enabled..." cmake --preset default -DBUILD_ELECTRON_APP=ON $EXTRA_CMAKE_ARGS elif [ -n "$EXTRA_CMAKE_ARGS" ]; then cmake --preset default $EXTRA_CMAKE_ARGS fi # Build (BUILD_ELECTRON_APP will trigger electron build automatically) echo "Building binaries..." cmake --build --preset default # Verify binaries exist if [ ! -f "build/lemonade-router" ]; then echo "ERROR: lemonade-router not found!" echo "Build directory contents:" ls -lh build/ exit 1 fi if [ ! -f "build/lemonade-server" ]; then echo "ERROR: lemonade-server not found!" echo "Build directory contents:" ls -lh build/ exit 1 fi echo "Verifying binary executability..." ./build/lemonade-router --version ./build/lemonade-server --version # Check if binaries are signed (non-fatal) echo "Checking code signatures..." codesign --verify --verbose=2 build/lemonade-router 2>&1 || echo "WARNING: lemonade-router not signed (expected for non-release builds)" codesign --verify --verbose=2 build/lemonade-server 2>&1 || echo "WARNING: lemonade-server not signed (expected for non-release builds)" echo "C++ build successful!" - name: Build Web App shell: bash run: | set -e echo "Building Web app..." cd src/web-app # Install dependencies echo "Installing npm dependencies..." npm install # Build the Web app echo "Building Web app..." npm run build # Verify the build output exists if [ ! -f "dist/renderer/index.html" ]; then echo "ERROR: Web app build output not found!" exit 1 fi # Copy web app output to the location expected by CMake echo "Copying web app to build/resources/web-app..." TARGET_DIR="../../build/resources/web-app" mkdir -p "$(dirname "$TARGET_DIR")" if [ -d "$TARGET_DIR" ]; then rm -rf "$TARGET_DIR" fi cp -R dist/renderer "$TARGET_DIR" # Verify the copy succeeded if [ ! -f "../../build/resources/web-app/index.html" ]; then echo "ERROR: Failed to copy web app to build directory!" exit 1 fi echo "Web app built and copied successfully!" - name: Build .pkg and notarize if: inputs.skip-packaging != 'true' id: build-dmg shell: bash run: | set -e echo "Building macOS .pkg via CMake notarize target..." echo "This will: build electron app, sign binaries, create package, notarize, and staple." if ! cmake --build --preset default --target notarize; then echo "" echo "=========================================" echo "Notarize target failed - fetching Apple notarization log..." echo "=========================================" # Find the submission ID from notarytool output and fetch the log SUBMISSION_ID=$(xcrun notarytool history --keychain-profile AC_PASSWORD 2>/dev/null | grep -m1 "id:" | awk '{print $2}' || echo "") if [ -n "$SUBMISSION_ID" ]; then echo "Fetching log for submission: $SUBMISSION_ID" xcrun notarytool log "$SUBMISSION_ID" --keychain-profile AC_PASSWORD 2>&1 || true else echo "Could not retrieve submission ID for log lookup" xcrun notarytool history --keychain-profile AC_PASSWORD 2>&1 || true fi exit 1 fi # Find the generated .pkg PKG_FILE=$(find build -maxdepth 1 -name "*.pkg" -type f | head -1) if [ -z "$PKG_FILE" ]; then echo "ERROR: No .pkg file found after build!" echo "Contents of build directory:" ls -lh build/*.pkg build/*.dmg 2>/dev/null || true exit 1 fi ARTIFACT_NAME=$(basename "$PKG_FILE") echo "Package created: $ARTIFACT_NAME (at $PKG_FILE)" echo "artifact_name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT echo "File details:" ls -lh "$PKG_FILE" echo "Build and notarization complete!" lemonade-sdk-lemonade-d88f5d9/.github/actions/cleanup-processes-linux/000077500000000000000000000000001515430344400260675ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/cleanup-processes-linux/action.yml000066400000000000000000000112111515430344400300630ustar00rootroot00000000000000name: 'Cleanup Processes (Linux)' description: 'Kill all lemonade, Python, and llama-server processes on Linux' runs: using: "composite" steps: - name: Kill zombie processes shell: bash run: | echo "========================================" echo "Cleaning up processes on Linux..." echo "========================================" # Kill processes aggressively by pattern kill_by_pattern() { local pattern=$1 local description=$2 echo "" echo "Killing $description processes..." # Find and kill in one shot with SIGKILL pkill -9 -f "$pattern" 2>/dev/null && echo " Killed processes matching: $pattern" || echo " No $description processes found" } # Kill by exact name (even more aggressive) kill_by_exact_name() { local name=$1 if pgrep -x "$name" > /dev/null 2>&1; then echo "Killing $name (exact match)..." pkill -9 -x "$name" sleep 0.5 # Verify it's dead if pgrep -x "$name" > /dev/null 2>&1; then echo " WARNING: $name still running after kill!" else echo " Successfully killed $name" fi fi } # Kill llama processes kill_by_pattern "llama-server" "llama-server" kill_by_pattern "llama-cli" "llama-cli" kill_by_pattern "llama-bench" "llama-bench" # Kill everything lemonade-related kill_by_pattern "lemonade" "lemonade" # Kill Python processes (this is aggressive - be careful!) kill_by_pattern "python" "Python" # Additional pass: kill by exact process names echo "" echo "Second pass: killing by exact name..." for proc_name in lemonade-server lemonade-router lemonade-server-dev lemonade-tray llama-server llama-cli llama-bench; do kill_by_exact_name "$proc_name" done # Wait for processes to die sleep 2 # FINAL VERIFICATION - show what's still alive echo "" echo "=== Verification: checking for remaining processes ===" if pgrep -f "lemonade|llama" > /dev/null 2>&1; then echo "WARNING: Some processes still alive:" ps aux | grep -E "lemonade|llama" | grep -v grep | grep -v "cleanup-processes" || true # NUCLEAR OPTION: Kill them with extreme prejudice echo "Using nuclear option..." pkill -9 -f "llama" 2>/dev/null || true pkill -9 -f "lemonade" 2>/dev/null || true sleep 1 # Check again if pgrep -f "lemonade|llama" > /dev/null 2>&1; then echo "ERROR: Processes STILL alive after nuclear option!" ps aux | grep -E "lemonade|llama" | grep -v grep || true else echo "Nuclear option successful" fi else echo "All target processes terminated successfully" fi # Clean up lock files and PID files # Check both /tmp (fallback) and $XDG_RUNTIME_DIR/lemonade (when set) echo "" echo "Cleaning up lock and PID files..." rm -f /tmp/lemonade*.lock 2>/dev/null || true rm -f /tmp/lemonade*.pid 2>/dev/null || true rm -f /tmp/lemonade-router.pid 2>/dev/null || true if [ -n "${XDG_RUNTIME_DIR:-}" ]; then rm -f "${XDG_RUNTIME_DIR}/lemonade/lemonade"*.lock 2>/dev/null || true rm -f "${XDG_RUNTIME_DIR}/lemonade/lemonade"*.pid 2>/dev/null || true fi echo "Lock and PID files cleaned." # Clean up log files echo "" echo "Cleaning up log files..." rm -f /tmp/lemonade*.log 2>/dev/null || true rm -f /tmp/lemonade-server*.log 2>/dev/null || true rm -f /tmp/lemonade-router*.log 2>/dev/null || true if [ -n "${XDG_RUNTIME_DIR:-}" ]; then rm -f "${XDG_RUNTIME_DIR}/lemonade/lemonade"*.log 2>/dev/null || true fi echo "Log files cleaned." # Clean up installation directories echo "" echo "Cleaning up lemonade installation directories..." if [ -d "$HOME/.local/share/lemonade-server" ]; then rm -rf "$HOME/.local/share/lemonade-server" 2>/dev/null || true echo "Removed: $HOME/.local/share/lemonade-server" else echo "No lemonade-server directory found in ~/.local/share/" fi echo "" echo "========================================" echo "Process cleanup complete!" echo "========================================" lemonade-sdk-lemonade-d88f5d9/.github/actions/cleanup-processes-windows/000077500000000000000000000000001515430344400264225ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/cleanup-processes-windows/action.yml000066400000000000000000000217601515430344400304300ustar00rootroot00000000000000name: 'Cleanup Processes (Windows)' description: 'Kill all lemonade, Python, llama-server, and FLM processes on Windows' runs: using: "composite" steps: - name: Kill zombie processes shell: PowerShell run: | Write-Host "========================================" -ForegroundColor Cyan Write-Host "Cleaning up processes on Windows..." -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan # Function to safely kill processes by name pattern function Stop-ProcessByPattern { param( [string]$Pattern, [string]$Description ) Write-Host "`nSearching for $Description processes..." -ForegroundColor Yellow $processes = Get-Process | Where-Object { $_.ProcessName -like "*$Pattern*" } if ($processes) { Write-Host "Found $($processes.Count) $Description process(es):" -ForegroundColor Yellow $processes | ForEach-Object { Write-Host " - $($_.ProcessName) (PID: $($_.Id))" -ForegroundColor Gray } $processes | ForEach-Object { try { Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue Write-Host " Killed: $($_.ProcessName) (PID: $($_.Id))" -ForegroundColor Green } catch { Write-Host " Failed to kill: $($_.ProcessName) (PID: $($_.Id)) - $_" -ForegroundColor Red } } } else { Write-Host "No $Description processes found." -ForegroundColor Gray } } # Kill llama-server processes Stop-ProcessByPattern -Pattern "llama-server" -Description "llama-server" Stop-ProcessByPattern -Pattern "llama" -Description "llama" # Kill FLM processes (Windows only) Stop-ProcessByPattern -Pattern "flm" -Description "FLM" # Kill lemonade processes (lemonade-server, lemonade-router, lemonade-server-dev, etc.) Stop-ProcessByPattern -Pattern "lemonade" -Description "lemonade" # Kill Python processes Stop-ProcessByPattern -Pattern "python" -Description "Python" # Kill wscript processes (VBScript launcher used by lemonade) Stop-ProcessByPattern -Pattern "wscript" -Description "wscript" # Wait a moment for processes to fully terminate Start-Sleep -Seconds 2 # Attempt to uninstall legacy C++ NSIS-based Lemonade Server, if present Write-Host "`nChecking for legacy Lemonade Server NSIS installer..." -ForegroundColor Yellow $nsisKey = 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\Lemonade Server' if (Test-Path $nsisKey) { try { $nsisProps = Get-ItemProperty -Path $nsisKey -ErrorAction Stop $uninstallExe = $nsisProps.UninstallString if ($uninstallExe) { Write-Host " Found NSIS uninstall registry entry. UninstallString: $uninstallExe" -ForegroundColor Gray if (Test-Path $uninstallExe) { Write-Host " Running NSIS uninstaller silently..." -ForegroundColor Yellow try { & $uninstallExe /S | Out-Null Write-Host " NSIS uninstaller process invoked." -ForegroundColor Green } catch { Write-Host " NSIS uninstaller execution failed: $_" -ForegroundColor Red } } else { Write-Host " NSIS uninstall executable path is stale (file not found). Treating as already removed." -ForegroundColor Gray } } else { Write-Host " NSIS uninstall string missing in registry. Treating as stale entry." -ForegroundColor Gray } # After running (or skipping) the uninstaller, ensure the ARP key is gone so MSI is not blocked if (Test-Path $nsisKey) { Write-Host " Removing stale NSIS ARP registry key to unblock MSI install..." -ForegroundColor Yellow try { Remove-Item -Path $nsisKey -Recurse -Force -ErrorAction Stop Write-Host " NSIS ARP registry key removed." -ForegroundColor Green } catch { Write-Host " Failed to remove NSIS ARP registry key: $_" -ForegroundColor Red } } else { Write-Host " NSIS ARP registry key already removed by uninstaller." -ForegroundColor Green } } catch { Write-Host " Failed to query NSIS uninstall key: $_" -ForegroundColor Red } } else { Write-Host " No legacy C++ NSIS installer registry key detected in HKCU (already clean)" -ForegroundColor Gray } # Clean up Python user directory that can cause issues Write-Host "`nCleaning up system Python directory..." -ForegroundColor Yellow $systemPythonPath = "C:\windows\system32\config\systemprofile\AppData\Roaming\Python" if (Test-Path $systemPythonPath) { try { Remove-Item -Path $systemPythonPath -Recurse -Force -ErrorAction SilentlyContinue Write-Host " Cleaned: $systemPythonPath" -ForegroundColor Green } catch { Write-Host " Failed to clean: $systemPythonPath - $_" -ForegroundColor Red } } else { Write-Host " System Python directory not found (already clean)" -ForegroundColor Gray } # Clean up log files to avoid confusion between test runs Write-Host "`nCleaning up log files..." -ForegroundColor Yellow $logPatterns = @("lemonade*.log", "lemonade-router*.log", "lemonade-server*.log") foreach ($pattern in $logPatterns) { $logFiles = Get-ChildItem "$env:TEMP\$pattern" -ErrorAction SilentlyContinue if ($logFiles) { Write-Host " Found $($logFiles.Count) log file(s) matching '$pattern'" -ForegroundColor Gray $logFiles | ForEach-Object { try { Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue Write-Host " Removed: $($_.Name)" -ForegroundColor Green } catch { Write-Host " Failed to remove: $($_.Name)" -ForegroundColor Red } } } } Write-Host "`n========================================" -ForegroundColor Cyan Write-Host "Process cleanup complete!" -ForegroundColor Cyan Write-Host "========================================" -ForegroundColor Cyan - name: Uninstall Lemonade Server (MSI) shell: PowerShell run: | Write-Host "`n========================================" -ForegroundColor Magenta Write-Host "Uninstalling Lemonade Server (MSI)..." -ForegroundColor Magenta Write-Host "========================================" -ForegroundColor Magenta # Uninstall by Product Code - Attempt to find it first $productName = "Lemonade Server" Write-Host "Searching for product: $productName" -ForegroundColor Cyan $product = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name -eq $productName } if ($product) { Write-Host "Found installed product: $($product.Name) (Version: $($product.Version))" -ForegroundColor Green Write-Host "IdentifyingNumber (ProductCode): $($product.IdentifyingNumber)" -ForegroundColor Gray Write-Host "Uninstalling..." -ForegroundColor Yellow $proc = Start-Process -FilePath "msiexec.exe" -ArgumentList "/x $($product.IdentifyingNumber) /qn" -Wait -PassThru if ($proc.ExitCode -eq 0) { Write-Host "Uninstall successful!" -ForegroundColor Green } else { Write-Host "Uninstall failed with exit code $($proc.ExitCode)" -ForegroundColor Red } } else { Write-Host "Product '$productName' not found installed via MSI." -ForegroundColor Gray # Fallback: Check if we can uninstall via the MSI file if it exists in current directory if (Test-Path "lemonade-server-minimal.msi") { Write-Host "Found lemonade-server-minimal.msi in current directory. Attempting uninstall via file..." -ForegroundColor Yellow $proc = Start-Process -FilePath "msiexec.exe" -ArgumentList "/x lemonade-server-minimal.msi /qn" -Wait -PassThru if ($proc.ExitCode -eq 0) { Write-Host "Uninstall via MSI file successful!" -ForegroundColor Green } else { Write-Host "Uninstall via MSI file failed with exit code $($proc.ExitCode)" -ForegroundColor Red } } } lemonade-sdk-lemonade-d88f5d9/.github/actions/generate-release-notes/000077500000000000000000000000001515430344400256355ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/generate-release-notes/action.yml000066400000000000000000000155231515430344400276430ustar00rootroot00000000000000name: 'Generate Release Notes' description: 'Generate formatted release notes with download table and contributor info' inputs: version: description: 'The version string (e.g., 9.1.3)' required: true repo: description: 'Repository in owner/repo format' required: true github_token: description: 'GitHub token for API access' required: true outputs: release_notes_file: description: 'Path to the release notes file' value: ${{ steps.generate.outputs.release_notes_file }} runs: using: "composite" steps: - name: Generate release notes id: generate shell: bash env: GH_TOKEN: ${{ inputs.github_token }} VERSION: ${{ inputs.version }} REPO: ${{ inputs.repo }} run: | set -e TAG_NAME="v$VERSION" echo "Generating release notes for $TAG_NAME" # Check if tag exists via GitHub API (works without local git tags) TAG_EXISTS=$(gh api repos/$REPO/git/refs/tags/$TAG_NAME --jq '.ref' 2>/dev/null || echo "") # Check if this tag already has a release on GitHub RELEASE_EXISTS=$(gh api repos/$REPO/releases/tags/$TAG_NAME --jq '.tag_name' 2>/dev/null || echo "") if [ -n "$TAG_EXISTS" ]; then # Tag exists on GitHub - use it as target (covers both new and existing releases) TARGET_COMMIT="$TAG_NAME" if [ -n "$RELEASE_EXISTS" ]; then # Release already exists - find the release before this one echo "Tag $TAG_NAME exists with release, finding previous release..." PREVIOUS_TAG=$(gh api repos/$REPO/releases --jq "[.[] | select(.tag_name != \"$TAG_NAME\")] | .[0].tag_name" 2>/dev/null || echo "") else # New release - use latest release as previous echo "Tag $TAG_NAME exists on GitHub, creating new release..." PREVIOUS_TAG=$(gh api repos/$REPO/releases/latest --jq '.tag_name' 2>/dev/null || echo "") fi else # Tag doesn't exist yet - preview mode, use HEAD echo "Tag $TAG_NAME doesn't exist yet, using HEAD for preview" PREVIOUS_TAG=$(gh api repos/$REPO/releases/latest --jq '.tag_name' 2>/dev/null || echo "") TARGET_COMMIT="HEAD" fi echo "Previous release: ${PREVIOUS_TAG:-none}" echo "Target commit: $TARGET_COMMIT" # Generate auto release notes from GitHub if [ -n "$PREVIOUS_TAG" ]; then AUTO_NOTES=$(gh api repos/$REPO/releases/generate-notes \ -f tag_name="$TAG_NAME" \ -f target_commitish="$TARGET_COMMIT" \ -f previous_tag_name="$PREVIOUS_TAG" \ --jq '.body' 2>/dev/null || echo "") else AUTO_NOTES=$(gh api repos/$REPO/releases/generate-notes \ -f tag_name="$TAG_NAME" \ -f target_commitish="$TARGET_COMMIT" \ --jq '.body' 2>/dev/null || echo "") fi echo "AUTO_NOTES length: ${#AUTO_NOTES}" echo "=== AUTO_NOTES content ===" echo "$AUTO_NOTES" echo "=== end AUTO_NOTES ===" # Extract What's Changed content (just the bullet points) WHATS_CHANGED_CONTENT="" if echo "$AUTO_NOTES" | grep -q "What's Changed"; then WHATS_CHANGED_CONTENT=$(echo "$AUTO_NOTES" | sed -n "/## What's Changed/,/## New Contributors\|## Full Changelog\|^$/p" | grep "^\* " || echo "") fi # Extract New Contributors section NEW_CONTRIBUTORS="" if echo "$AUTO_NOTES" | grep -q "New Contributors"; then NEW_CONTRIBUTORS=$(echo "$AUTO_NOTES" | sed -n "/## New Contributors/,/## Full Changelog\|^$/p" | head -20) fi # Extract Full Changelog link FULL_CHANGELOG=$(echo "$AUTO_NOTES" | grep "Full Changelog" | head -1 || echo "") # Extract unique contributors from the changelog (exclude bots) CONTRIBUTORS=$(echo "$AUTO_NOTES" | grep -oE '@[a-zA-Z0-9_-]+' | grep -ivE '^@(copilot|claude)$' | sort -u | tr '\n' ' ' | sed 's/ /, /g' | sed 's/, $//' || echo "") # Build the release notes file RELEASE_NOTES_FILE="release_notes.md" { echo "## Quick Install" echo "" echo "| Operating System | Downloads |" echo "|------------------|-----------|" echo "| **Windows** | [lemonade.msi](https://github.com/$REPO/releases/download/$TAG_NAME/lemonade.msi) (Server + App) · [lemonade-server-minimal.msi](https://github.com/$REPO/releases/download/$TAG_NAME/lemonade-server-minimal.msi) (Server Only) |" echo "| **Ubuntu** | [lemonade-server_${VERSION}_amd64.deb](https://github.com/$REPO/releases/download/$TAG_NAME/lemonade-server_${VERSION}_amd64.deb) (Server) · [Lemonade-${VERSION}-x86_64.AppImage](https://github.com/$REPO/releases/download/$TAG_NAME/Lemonade-${VERSION}-x86_64.AppImage) (Standalone App) |" echo "| **macOS (beta)** | [Lemonade-${VERSION}-Darwin.pkg](https://github.com/$REPO/releases/download/$TAG_NAME/Lemonade-${VERSION}-Darwin.pkg) (Server + App) |" echo "" echo "> **Other platforms?** See our [Installation Options](https://lemonade-server.ai/install_options.html) for [Docker](https://lemonade-server.ai/install_options.html#docker), [Snap](https://lemonade-server.ai/install_options.html#ubuntu), [Arch](https://lemonade-server.ai/install_options.html#arch), [Fedora](https://lemonade-server.ai/install_options.html#fedora), [Debian](https://lemonade-server.ai/install_options.html#debian), and more." echo "" echo "---" echo "" } > "$RELEASE_NOTES_FILE" # Build What's Changed section with thank you and collapsible details if [ -n "$WHATS_CHANGED_CONTENT" ]; then { echo "## What's Changed" echo "" if [ -n "$CONTRIBUTORS" ]; then echo "Thanks $CONTRIBUTORS for your awesome contributions to this release!" echo "" fi echo "
" echo "Click to expand changelog" echo "" echo "$WHATS_CHANGED_CONTENT" echo "" echo "
" echo "" } >> "$RELEASE_NOTES_FILE" fi [ -n "$NEW_CONTRIBUTORS" ] && echo "$NEW_CONTRIBUTORS" >> "$RELEASE_NOTES_FILE" && echo "" >> "$RELEASE_NOTES_FILE" [ -n "$FULL_CHANGELOG" ] && echo "$FULL_CHANGELOG" >> "$RELEASE_NOTES_FILE" # Code signing policy note { echo "" echo "---" echo "" echo "> Windows installers are signed. Free code signing provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org). See our [Code Signing Policy](https://github.com/$REPO#code-signing-policy)." } >> "$RELEASE_NOTES_FILE" echo "release_notes_file=$RELEASE_NOTES_FILE" >> $GITHUB_OUTPUT cat "$RELEASE_NOTES_FILE" lemonade-sdk-lemonade-d88f5d9/.github/actions/get-version/000077500000000000000000000000001515430344400235415ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/get-version/action.yml000066400000000000000000000014521515430344400255430ustar00rootroot00000000000000name: 'Get Lemonade Version' description: 'Extract version from CMakeLists.txt - single source of truth' outputs: version: description: 'The version string (e.g., 9.0.0)' value: ${{ steps.extract.outputs.version }} runs: using: "composite" steps: - name: Extract version from CMakeLists.txt id: extract shell: bash run: | set -e # Extract version from CMakeLists.txt VERSION=$(sed -n 's/^project(lemon_cpp VERSION \([0-9]*\.[0-9]*\.[0-9]*\).*/\1/p' CMakeLists.txt) if [ -z "$VERSION" ]; then echo "ERROR: Could not extract version from CMakeLists.txt" exit 1 fi echo "Extracted version: $VERSION" echo "version=$VERSION" >> $GITHUB_OUTPUT echo "LEMONADE_VERSION=$VERSION" >> $GITHUB_ENV lemonade-sdk-lemonade-d88f5d9/.github/actions/install-lemonade-deb/000077500000000000000000000000001515430344400252575ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/install-lemonade-deb/action.yml000066400000000000000000000054321515430344400272630ustar00rootroot00000000000000name: 'Install Lemonade (.deb)' description: 'Downloads and extracts Lemonade .deb package, then verifies installation' inputs: version: description: 'Version string for the .deb package (e.g., 0.5.0)' required: true artifact-name: description: 'Name of the artifact to download' required: false default: 'lemonade-deb' extract-path: description: 'Path where the .deb should be extracted' required: false default: './deb-extract' download-artifact: description: 'Whether to download the artifact (set to false if already downloaded)' required: false default: 'true' outputs: bin-path: description: 'Path to the extracted binaries directory' value: ${{ steps.extract.outputs.bin-path }} runs: using: "composite" steps: - name: Download .deb package if: ${{ inputs.download-artifact == 'true' }} uses: actions/download-artifact@v4 with: name: ${{ inputs.artifact-name }} path: . - name: Extract .deb package id: extract shell: bash run: | set -e VERSION="${{ inputs.version }}" EXTRACT_PATH="${{ inputs.extract-path }}" DEB_FILE="lemonade-server_${VERSION}_amd64.deb" echo "Extracting .deb package: $DEB_FILE" if [ ! -f "$DEB_FILE" ]; then echo "ERROR: .deb file not found: $DEB_FILE" ls -la *.deb 2>/dev/null || echo "No .deb files found in current directory" exit 1 fi # Extract the .deb package mkdir -p "$EXTRACT_PATH" dpkg-deb -x "$DEB_FILE" "$EXTRACT_PATH" # Copy resources to user's local share (path_utils.cpp checks here) mkdir -p ~/.local/share/lemonade-server cp -r "$EXTRACT_PATH/opt/share/lemonade-server/"* ~/.local/share/lemonade-server/ # Make llama directory writable for backend downloads mkdir -p "$EXTRACT_PATH/opt/share/lemonade-server/llama" chmod 777 "$EXTRACT_PATH/opt/share/lemonade-server/llama" # Make binaries executable chmod +x "$EXTRACT_PATH/opt/bin/lemonade-server" chmod +x "$EXTRACT_PATH/opt/bin/lemonade-router" # Add binaries to PATH for subsequent steps BIN_PATH="$(pwd)/$EXTRACT_PATH/opt/bin" echo "$BIN_PATH" >> $GITHUB_PATH echo "bin-path=$BIN_PATH" >> $GITHUB_OUTPUT echo "Package extracted successfully to: $EXTRACT_PATH" echo "Binaries available at: $BIN_PATH" - name: Verify binaries work shell: bash run: | set -e EXTRACT_PATH="${{ inputs.extract-path }}" echo "Verifying binaries..." # Test version commands "$EXTRACT_PATH/opt/bin/lemonade-router" --version "$EXTRACT_PATH/opt/bin/lemonade-server" --version echo "Binaries verified successfully!" lemonade-sdk-lemonade-d88f5d9/.github/actions/install-lemonade-server-dmg/000077500000000000000000000000001515430344400266005ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/install-lemonade-server-dmg/action.yml000066400000000000000000000057251515430344400306110ustar00rootroot00000000000000name: 'Install Lemonade Server (.pkg)' description: 'Downloads and installs Lemonade Server from a .pkg, then verifies installation' inputs: version: description: 'Version string for the package (e.g., 9.3.1)' required: true artifact-name: description: 'Name of the artifact to download' required: false default: 'lemonade-macos-pkg' download-artifact: description: 'Whether to download the artifact (set to false if already downloaded)' required: false default: 'true' outputs: bin-path: description: 'Path to the installed binaries directory' value: ${{ steps.install.outputs.bin-path }} runs: using: "composite" steps: - name: Download .pkg package if: ${{ inputs.download-artifact == 'true' }} uses: actions/download-artifact@v4 with: name: ${{ inputs.artifact-name }} path: . - name: Install .pkg id: install shell: bash run: | set -e echo "Looking for .pkg file..." PKG_FILE=$(find . -maxdepth 1 -name "*.pkg" -type f | head -1) if [ -z "$PKG_FILE" ]; then echo "ERROR: No .pkg file found in current directory!" ls -la exit 1 fi echo "Installing: $PKG_FILE" sudo installer -pkg "$PKG_FILE" -target / echo "Installation complete" # Binaries are installed at /usr/local/bin BIN_PATH="/usr/local/bin" echo "$BIN_PATH" >> $GITHUB_PATH echo "bin-path=$BIN_PATH" >> $GITHUB_OUTPUT echo "Binaries at: $BIN_PATH" - name: Stop services before verification shell: bash run: | echo "Stopping any services started by postflight script..." sudo launchctl bootout system/com.lemonade.server 2>/dev/null || true sudo launchctl bootout system/com.lemonade.tray 2>/dev/null || true sleep 2 # Clean up PID and lock files left behind by killed processes echo "Cleaning up stale PID and lock files..." sudo rm -f /tmp/lemonade-router.pid sudo rm -f /tmp/lemonade_Router.lock sudo rm -f /tmp/lemonade_Server.lock sudo rm -f /tmp/lemonade_Tray.lock sudo rm -f /tmp/lemonade*.pid /tmp/lemonade*.lock echo "Services stopped and cleaned up." - name: Verify installation shell: bash run: | set -e BIN_PATH="${{ steps.install.outputs.bin-path }}" echo "Verifying installation at: $BIN_PATH" if [ -x "$BIN_PATH/lemonade-server" ]; then echo "Found: lemonade-server" "$BIN_PATH/lemonade-server" --version else echo "ERROR: lemonade-server not found at $BIN_PATH" exit 1 fi if [ -x "$BIN_PATH/lemonade-router" ]; then echo "Found: lemonade-router" "$BIN_PATH/lemonade-router" --version else echo "WARNING: lemonade-router not found at $BIN_PATH" fi echo "Installation verification complete!" lemonade-sdk-lemonade-d88f5d9/.github/actions/install-lemonade-server-msi/000077500000000000000000000000001515430344400266215ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/install-lemonade-server-msi/action.yml000066400000000000000000000117311515430344400306240ustar00rootroot00000000000000name: 'Install Lemonade Server (MSI)' description: 'Downloads and installs Lemonade Server MSI, then verifies installation' inputs: install-path: description: 'Path where Lemonade Server should be installed' required: true artifact-name: description: 'Name of the artifact to download' required: false default: 'Lemonade_Server_MSI' runs: using: "composite" steps: - name: Download Lemonade Server Installer uses: actions/download-artifact@v4 with: name: ${{ inputs.artifact-name }} path: . - name: Install Lemonade Server shell: PowerShell run: | $installPath = "${{ inputs.install-path }}" $logPath = "$((Get-Location).Path)\lemonade-msi-install.log" Write-Host "Installing Lemonade Server to: $installPath" -ForegroundColor Cyan Write-Host "Log file will be at: $logPath" -ForegroundColor Gray if (-not (Test-Path "lemonade-server-minimal.msi")) { Write-Host "ERROR: lemonade-server-minimal.msi not found in current directory!" -ForegroundColor Red Get-ChildItem exit 1 } # Run installer silently with verbose logging $args = "/i lemonade-server-minimal.msi /qn INSTALLDIR=`"$installPath`" /L*V `"$logPath`"" $p = Start-Process -FilePath "msiexec.exe" -ArgumentList $args -Wait -PassThru if ($p.ExitCode -ne 0) { Write-Host "ERROR: Installation failed with exit code $($p.ExitCode)" -ForegroundColor Red if (Test-Path $logPath) { Write-Host "=== MSI Install Log ($logPath) ===" -ForegroundColor Yellow Get-Content $logPath Write-Host "=== End MSI Install Log ===" -ForegroundColor Yellow } else { Write-Host "ERROR: Log file not found at $logPath" -ForegroundColor Red } exit $p.ExitCode } Write-Host "Installation complete." -ForegroundColor Green - name: Verify Installation shell: PowerShell run: | $ErrorActionPreference = "Stop" $installPath = "${{ inputs.install-path }}" $logPath = "$((Get-Location).Path)\lemonade-msi-install.log" Write-Host "Verifying installation at: $installPath" -ForegroundColor Cyan # Function to print log on failure function Print-InstallLog { if (Test-Path $logPath) { Write-Host "=== MSI Install Log ($logPath) ===" -ForegroundColor Yellow Get-Content $logPath Write-Host "=== End MSI Install Log ===" -ForegroundColor Yellow } else { Write-Host "Log file not found at: $logPath" -ForegroundColor Red } } # Check if installation directory exists if (-not (Test-Path $installPath)) { Write-Host "ERROR: Installation directory not found at $installPath" -ForegroundColor Red Print-InstallLog exit 1 } # Check for key binaries $expectedBinaries = @( "bin\lemonade-router.exe", "bin\lemonade-server.exe", "bin\lemonade-tray.exe" ) $missingItems = @() foreach ($item in $expectedBinaries) { $fullPath = Join-Path $installPath $item if (-not (Test-Path $fullPath)) { $missingItems += $item } else { Write-Host "Found: $item" -ForegroundColor Green } } if ($missingItems.Count -gt 0) { Write-Host "ERROR: Missing expected files after installation:" -ForegroundColor Red foreach ($missing in $missingItems) { Write-Host " Missing: $missing" -ForegroundColor Red } Print-InstallLog exit 1 } Write-Host "Installation directory verification successful!" -ForegroundColor Green - name: Verify Server Version shell: PowerShell run: | $ErrorActionPreference = "Stop" $installPath = "${{ inputs.install-path }}" $serverExe = Join-Path $installPath "bin\lemonade-server.exe" Write-Host "Checking lemonade-server version..." -ForegroundColor Cyan try { # Capture output and exit code $process = Start-Process -FilePath $serverExe -ArgumentList "--version" -NoNewWindow -Wait -PassThru if ($process.ExitCode -ne 0) { Write-Host "ERROR: lemonade-server --version exited with code $($process.ExitCode)" -ForegroundColor Red exit 1 } # Run again to capture output for display (Start-Process -PassThru doesn't easily capture stdout to variable without redirect) # Simpler approach for display: & $serverExe --version Write-Host "Version check passed (Exit Code 0)" -ForegroundColor Green } catch { Write-Host "ERROR: Failed to run version check: $_" -ForegroundColor Red exit 1 } lemonade-sdk-lemonade-d88f5d9/.github/actions/setup-macos-keychain/000077500000000000000000000000001515430344400253305ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/setup-macos-keychain/action.yml000066400000000000000000000120051515430344400273260ustar00rootroot00000000000000name: 'Setup macOS Keychain for Signing' description: 'Import Developer ID certificates and store notarization credentials using App Store Connect API key' inputs: dev-signing-key: description: 'Base64-encoded Developer ID Application certificate (.p12)' required: true inst-signing-key: description: 'Base64-encoded Developer ID Installer certificate (.p12)' required: true certificate-password: description: 'Password for the .p12 files' required: true api-key: description: 'App Store Connect API key (.p8 content, base64-encoded)' required: true api-key-id: description: 'App Store Connect API key ID' required: true api-issuer-id: description: 'App Store Connect Issuer ID' required: true runs: using: "composite" steps: - name: Import certificates and store notarization credentials shell: bash run: | set -e echo "========================================" echo "Setting up macOS Keychain for signing..." echo "========================================" # Create a temporary keychain KEYCHAIN_PATH="$RUNNER_TEMP/signing.keychain-db" KEYCHAIN_PASSWORD="$(openssl rand -hex 24)" echo "Creating temporary keychain..." security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" # Import Developer ID Application certificate echo "Importing Developer ID Application certificate..." DEV_CERT_PATH="$RUNNER_TEMP/dev_certificate.p12" echo "${{ inputs.dev-signing-key }}" | base64 --decode > "$DEV_CERT_PATH" security import "$DEV_CERT_PATH" \ -k "$KEYCHAIN_PATH" \ -P "${{ inputs.certificate-password }}" \ -T /usr/bin/codesign \ -T /usr/bin/productbuild \ -T /usr/bin/pkgbuild rm -f "$DEV_CERT_PATH" # Import Developer ID Installer certificate echo "Importing Developer ID Installer certificate..." INST_CERT_PATH="$RUNNER_TEMP/inst_certificate.p12" echo "${{ inputs.inst-signing-key }}" | base64 --decode > "$INST_CERT_PATH" security import "$INST_CERT_PATH" \ -k "$KEYCHAIN_PATH" \ -P "${{ inputs.certificate-password }}" \ -T /usr/bin/codesign \ -T /usr/bin/productbuild \ -T /usr/bin/pkgbuild rm -f "$INST_CERT_PATH" # Set partition list to allow codesign access without UI prompt security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" # Add temporary keychain to search list (prepend so it takes priority) security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"') # Extract certificate fingerprints for CMake echo "Extracting certificate fingerprints..." # Get Developer ID Application identity DEV_IDENTITY=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" | grep "Developer ID Application" | head -1 | awk '{print $2}') if [ -z "$DEV_IDENTITY" ]; then echo "ERROR: Developer ID Application certificate not found in keychain!" security find-identity -v "$KEYCHAIN_PATH" exit 1 fi echo "Developer ID Application: $DEV_IDENTITY" # Get Developer ID Installer identity INST_IDENTITY=$(security find-identity -v "$KEYCHAIN_PATH" | grep "Developer ID Installer" | head -1 | awk '{print $2}') if [ -z "$INST_IDENTITY" ]; then echo "ERROR: Developer ID Installer certificate not found in keychain!" security find-identity -v "$KEYCHAIN_PATH" exit 1 fi echo "Developer ID Installer: $INST_IDENTITY" # Export for CMake (CMakeLists.txt reads these at lines 861 and 869) echo "DEVELOPER_ID_APPLICATION_IDENTITY=$DEV_IDENTITY" >> $GITHUB_ENV echo "DEVELOPER_ID_INSTALLER_IDENTITY=$INST_IDENTITY" >> $GITHUB_ENV # Write the App Store Connect API key to a file for notarytool echo "Setting up App Store Connect API key for notarization..." API_KEY_PATH="$RUNNER_TEMP/AuthKey_${{ inputs.api-key-id }}.p8" echo "${{ inputs.api-key }}" | base64 --decode > "$API_KEY_PATH" # Store notarization credentials in keychain profile "AC_PASSWORD" # using App Store Connect API key (recommended by Apple for CI) # This matches the CMake notarize target at CMakeLists.txt:1026 xcrun notarytool store-credentials "AC_PASSWORD" \ --key "$API_KEY_PATH" \ --key-id "${{ inputs.api-key-id }}" \ --issuer "${{ inputs.api-issuer-id }}" rm -f "$API_KEY_PATH" # Export keychain path for cleanup echo "SIGNING_KEYCHAIN_PATH=$KEYCHAIN_PATH" >> $GITHUB_ENV echo "========================================" echo "Keychain setup complete!" echo "========================================" lemonade-sdk-lemonade-d88f5d9/.github/actions/setup-python/000077500000000000000000000000001515430344400237565ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/setup-python/action.yml000066400000000000000000000134351515430344400257640ustar00rootroot00000000000000name: 'Setup Python' description: 'Install Python on self-hosted runners. Uses full Python installer on Windows, checks system Python on Linux.' inputs: python-version: description: 'Python version to install (e.g., 3.10, 3.12)' required: false default: '3.10' outputs: python-path: description: 'Path to the Python executable' value: ${{ steps.setup-windows.outputs.python-path || steps.setup-linux.outputs.python-path }} runs: using: "composite" steps: - name: Setup Python (Windows) id: setup-windows if: runner.os == 'Windows' shell: powershell run: | $ErrorActionPreference = "Stop" $PYTHON_VERSION = "${{ inputs.python-version }}" Write-Host "Setting up Python $PYTHON_VERSION on Windows..." # Map version to download URL for full installer switch ($PYTHON_VERSION) { "3.10" { $PYTHON_FULL = "3.10.11" } "3.11" { $PYTHON_FULL = "3.11.9" } "3.12" { $PYTHON_FULL = "3.12.7" } "3.13" { $PYTHON_FULL = "3.13.0" } default { Write-Host "Unsupported Python version: $PYTHON_VERSION" exit 1 } } $PYTHON_DIR = "$env:GITHUB_WORKSPACE\.python-$PYTHON_VERSION" $PYTHON_EXE = "$PYTHON_DIR\python.exe" # Check if already installed if (Test-Path $PYTHON_EXE) { Write-Host "Python $PYTHON_VERSION already available at $PYTHON_DIR" } else { Write-Host "Downloading Python $PYTHON_FULL..." # Use nuget to install Python (more reliable than embeddable) $nugetUrl = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" $nugetPath = "$env:GITHUB_WORKSPACE\nuget.exe" Invoke-WebRequest -Uri $nugetUrl -OutFile $nugetPath # Install Python via nuget & $nugetPath install python -Version $PYTHON_FULL -OutputDirectory "$env:GITHUB_WORKSPACE\.nuget-packages" if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # Find the installed Python $pythonPackageDir = Get-ChildItem "$env:GITHUB_WORKSPACE\.nuget-packages" -Directory | Where-Object { $_.Name -like "python.$PYTHON_FULL" } | Select-Object -First 1 if (-not $pythonPackageDir) { Write-Host "ERROR: Python package not found after nuget install" Get-ChildItem "$env:GITHUB_WORKSPACE\.nuget-packages" exit 1 } $pythonToolsDir = Join-Path $pythonPackageDir.FullName "tools" # Move to our expected location if (Test-Path $PYTHON_DIR) { Remove-Item -Recurse -Force $PYTHON_DIR } Move-Item $pythonToolsDir $PYTHON_DIR # Ensure pip is installed Write-Host "Ensuring pip is installed..." & $PYTHON_EXE -m ensurepip --upgrade if ($LASTEXITCODE -ne 0) { Write-Host "ensurepip failed, trying get-pip.py..." Invoke-WebRequest -Uri "https://bootstrap.pypa.io/get-pip.py" -OutFile "$PYTHON_DIR\get-pip.py" & $PYTHON_EXE "$PYTHON_DIR\get-pip.py" } Write-Host "Python $PYTHON_FULL installed at $PYTHON_DIR" } # Add to PATH for subsequent steps "$PYTHON_DIR" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append "$PYTHON_DIR\Scripts" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append # Output the path "python-path=$PYTHON_EXE" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append # Verify & $PYTHON_EXE --version & $PYTHON_EXE -m pip --version - name: Setup Python (Linux/macOS) id: setup-linux if: runner.os != 'Windows' shell: bash run: | PYTHON_VERSION="${{ inputs.python-version }}" echo "Setting up Python $PYTHON_VERSION..." REQUIRED_MAJOR=$(echo $PYTHON_VERSION | cut -d. -f1) REQUIRED_MINOR=$(echo $PYTHON_VERSION | cut -d. -f2) # Try python3.X first, then python3, then python PYTHON_CMD="" for cmd in "python$PYTHON_VERSION" "python3" "python"; do if command -v "$cmd" &> /dev/null; then VERSION=$("$cmd" --version 2>&1 | grep -oP '\d+\.\d+' | head -1) MAJOR=$(echo $VERSION | cut -d. -f1) MINOR=$(echo $VERSION | cut -d. -f2) if [[ "$MAJOR" == "$REQUIRED_MAJOR" && "$MINOR" == "$REQUIRED_MINOR" ]]; then PYTHON_CMD=$(command -v "$cmd") echo "Found matching Python: $PYTHON_CMD (version $VERSION)" break elif [[ "$MAJOR" == "$REQUIRED_MAJOR" && "$MINOR" -ge "$REQUIRED_MINOR" ]]; then PYTHON_CMD=$(command -v "$cmd") echo "Found compatible Python: $PYTHON_CMD (version $VERSION, requested $PYTHON_VERSION)" break fi fi done if [ -z "$PYTHON_CMD" ]; then # Try to install from deadsnakes PPA (Ubuntu) echo "Python $PYTHON_VERSION not found. Attempting to install..." if command -v apt-get &> /dev/null; then sudo add-apt-repository -y ppa:deadsnakes/ppa 2>/dev/null || true sudo apt-get update sudo apt-get install -y "python${PYTHON_VERSION}" "python${PYTHON_VERSION}-venv" || { echo "Failed to install Python $PYTHON_VERSION from deadsnakes" echo "Falling back to system python3..." PYTHON_CMD=$(command -v python3) } PYTHON_CMD=$(command -v "python${PYTHON_VERSION}") || PYTHON_CMD=$(command -v python3) else PYTHON_CMD=$(command -v python3) fi fi if [ -z "$PYTHON_CMD" ]; then echo "ERROR: Could not find or install Python $PYTHON_VERSION" exit 1 fi echo "python-path=$PYTHON_CMD" >> $GITHUB_OUTPUT "$PYTHON_CMD" --version lemonade-sdk-lemonade-d88f5d9/.github/actions/setup-venv/000077500000000000000000000000001515430344400234135ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/actions/setup-venv/action.yml000066400000000000000000000222621515430344400254170ustar00rootroot00000000000000name: 'Setup Virtual Environment' description: 'Create a Python virtual environment in the checkout folder for clean isolation' inputs: venv-name: description: 'Name for the virtual environment directory (will be created in workspace)' required: false default: '.venv' python-version: description: 'Python version to use (passed to setup-python action on self-hosted)' required: false default: '3.10' requirements-file: description: 'Optional requirements file to install' required: false default: '' install-package: description: 'Optional package specification to install (e.g., ".[dev]" or "package-name")' required: false default: '' extra-index-url: description: 'Optional extra index URL for pip' required: false default: '' outputs: venv-path: description: 'Full path to the venv directory' value: ${{ steps.create-venv-windows.outputs.venv-path || steps.create-venv-linux.outputs.venv-path }} python-path: description: 'Path to the Python executable in the venv' value: ${{ steps.create-venv-windows.outputs.python-path || steps.create-venv-linux.outputs.python-path }} runs: using: "composite" steps: # For GitHub-hosted runners, use actions/setup-python - name: Setup Python (GitHub-hosted) if: ${{ !contains(runner.name, 'stx') && !contains(runner.name, 'rai') && !contains(runner.name, 'selfhost') }} uses: actions/setup-python@v5 with: python-version: ${{ inputs.python-version }} # For self-hosted runners, use our custom setup - name: Setup Python (self-hosted) id: setup-python-selfhosted if: ${{ contains(runner.name, 'stx') || contains(runner.name, 'rai') || contains(runner.name, 'selfhost') }} uses: ./.github/actions/setup-python with: python-version: ${{ inputs.python-version }} # Set the Python path for use in subsequent steps - name: Set Python path (self-hosted Windows) if: ${{ (contains(runner.name, 'stx') || contains(runner.name, 'rai') || contains(runner.name, 'selfhost')) && runner.os == 'Windows' }} shell: powershell run: | $PYTHON_DIR = "$env:GITHUB_WORKSPACE\.python-${{ inputs.python-version }}" $PYTHON_EXE = "$PYTHON_DIR\python.exe" Write-Host "Using Python at: $PYTHON_EXE" "SETUP_PYTHON_PATH=$PYTHON_EXE" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append - name: Set Python path (self-hosted Linux/macOS) if: ${{ (contains(runner.name, 'stx') || contains(runner.name, 'rai') || contains(runner.name, 'selfhost')) && runner.os != 'Windows' }} shell: bash run: | # Use the python that setup-python found/installed PYTHON_EXE="${{ steps.setup-python-selfhosted.outputs.python-path }}" echo "Using Python at: $PYTHON_EXE" echo "SETUP_PYTHON_PATH=$PYTHON_EXE" >> $GITHUB_ENV # Set Python path for GitHub-hosted runners (Windows) - name: Set Python path (GitHub-hosted Windows) if: ${{ !contains(runner.name, 'stx') && !contains(runner.name, 'rai') && !contains(runner.name, 'selfhost') && runner.os == 'Windows' }} shell: powershell run: | # On GitHub-hosted runners, actions/setup-python puts python in PATH $PYTHON_EXE = (Get-Command python).Source Write-Host "Using Python at: $PYTHON_EXE" "SETUP_PYTHON_PATH=$PYTHON_EXE" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append # Set Python path for GitHub-hosted runners (Linux/macOS) - name: Set Python path (GitHub-hosted Linux/macOS) if: ${{ !contains(runner.name, 'stx') && !contains(runner.name, 'rai') && !contains(runner.name, 'selfhost') && runner.os != 'Windows' }} shell: bash run: | # On GitHub-hosted runners, actions/setup-python puts python in PATH PYTHON_EXE=$(which python3 || which python) echo "Using Python at: $PYTHON_EXE" echo "SETUP_PYTHON_PATH=$PYTHON_EXE" >> $GITHUB_ENV - name: Create virtual environment (Windows) id: create-venv-windows if: runner.os == 'Windows' shell: powershell run: | $ErrorActionPreference = "Stop" $VENV_NAME = "${{ inputs.venv-name }}" $VENV_PATH = "$env:GITHUB_WORKSPACE\$VENV_NAME" $BASE_PYTHON = $env:SETUP_PYTHON_PATH Write-Host "Creating virtual environment at $VENV_PATH..." Write-Host "Using base Python: $BASE_PYTHON" # Verify we're using the correct Python & $BASE_PYTHON --version # Remove existing venv if present if (Test-Path $VENV_PATH) { Write-Host "Removing existing venv..." Remove-Item -Recurse -Force $VENV_PATH } # Create the venv using the explicit Python path & $BASE_PYTHON -m venv $VENV_PATH if ($LASTEXITCODE -ne 0) { Write-Host "venv creation failed, trying with --without-pip..." & $BASE_PYTHON -m venv $VENV_PATH --without-pip if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # Install pip manually Write-Host "Installing pip manually..." Invoke-WebRequest -Uri "https://bootstrap.pypa.io/get-pip.py" -OutFile "$VENV_PATH\get-pip.py" & "$VENV_PATH\Scripts\python.exe" "$VENV_PATH\get-pip.py" } $PYTHON_PATH = "$VENV_PATH\Scripts\python.exe" $PIP_PATH = "$VENV_PATH\Scripts\pip.exe" # PREPEND venv Scripts to PATH (write to file first, then prepend) # This ensures venv Python takes precedence over system Python "$VENV_PATH\Scripts" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append # Upgrade pip & $PYTHON_PATH -m pip install --upgrade pip "venv-path=$VENV_PATH" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append "python-path=$PYTHON_PATH" | Out-File -FilePath $env:GITHUB_OUTPUT -Encoding utf8 -Append Write-Host "Virtual environment created successfully!" Write-Host "Venv Python location:" & $PYTHON_PATH -c "import sys; print(sys.executable)" & $PYTHON_PATH --version - name: Create virtual environment (Linux/macOS) id: create-venv-linux if: runner.os != 'Windows' shell: bash run: | VENV_NAME="${{ inputs.venv-name }}" VENV_PATH="$GITHUB_WORKSPACE/$VENV_NAME" BASE_PYTHON="$SETUP_PYTHON_PATH" echo "Creating virtual environment at $VENV_PATH..." echo "Using base Python: $BASE_PYTHON" # Verify we're using the correct Python "$BASE_PYTHON" --version # Remove existing venv if present if [ -d "$VENV_PATH" ]; then echo "Removing existing venv..." rm -rf "$VENV_PATH" fi # Create the venv using the explicit Python path "$BASE_PYTHON" -m venv "$VENV_PATH" PYTHON_PATH="$VENV_PATH/bin/python" PIP_PATH="$VENV_PATH/bin/pip" # Add bin to PATH (prepended in subsequent steps) echo "$VENV_PATH/bin" >> $GITHUB_PATH # Upgrade pip "$PYTHON_PATH" -m pip install --upgrade pip echo "venv-path=$VENV_PATH" >> $GITHUB_OUTPUT echo "python-path=$PYTHON_PATH" >> $GITHUB_OUTPUT echo "Virtual environment created successfully!" echo "Venv Python location:" "$PYTHON_PATH" -c "import sys; print(sys.executable)" "$PYTHON_PATH" --version - name: Install requirements file (Windows) if: inputs.requirements-file != '' && runner.os == 'Windows' shell: powershell run: | $VENV_PATH = "${{ steps.create-venv-windows.outputs.venv-path }}" $PIP = "$VENV_PATH\Scripts\pip.exe" Write-Host "Installing requirements from ${{ inputs.requirements-file }}..." & $PIP install -r "${{ inputs.requirements-file }}" - name: Install requirements file (Linux/macOS) if: inputs.requirements-file != '' && runner.os != 'Windows' shell: bash run: | VENV_PATH="${{ steps.create-venv-linux.outputs.venv-path }}" PIP="$VENV_PATH/bin/pip" echo "Installing requirements from ${{ inputs.requirements-file }}..." "$PIP" install -r "${{ inputs.requirements-file }}" - name: Install package (Windows) if: inputs.install-package != '' && runner.os == 'Windows' shell: powershell run: | $VENV_PATH = "${{ steps.create-venv-windows.outputs.venv-path }}" $PIP = "$VENV_PATH\Scripts\pip.exe" $EXTRA_INDEX = "" if ("${{ inputs.extra-index-url }}" -ne "") { $EXTRA_INDEX = "--extra-index-url ${{ inputs.extra-index-url }}" } Write-Host "Installing package: ${{ inputs.install-package }}..." $cmd = "$PIP install ${{ inputs.install-package }} $EXTRA_INDEX" Invoke-Expression $cmd - name: Install package (Linux/macOS) if: inputs.install-package != '' && runner.os != 'Windows' shell: bash run: | VENV_PATH="${{ steps.create-venv-linux.outputs.venv-path }}" PIP="$VENV_PATH/bin/pip" EXTRA_INDEX="" if [ -n "${{ inputs.extra-index-url }}" ]; then EXTRA_INDEX="--extra-index-url ${{ inputs.extra-index-url }}" fi echo "Installing package: ${{ inputs.install-package }}..." "$PIP" install ${{ inputs.install-package }} $EXTRA_INDEX lemonade-sdk-lemonade-d88f5d9/.github/dependabot.yml000066400000000000000000000007231515430344400224710ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for more information: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates # https://containers.dev/guide/dependabot version: 2 updates: - package-ecosystem: "devcontainers" directory: "/" schedule: interval: weekly lemonade-sdk-lemonade-d88f5d9/.github/workflows/000077500000000000000000000000001515430344400216745ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.github/workflows/build-and-push-container.yml000066400000000000000000000033311515430344400272130ustar00rootroot00000000000000name: Build and Push Container Image on: push: tags: - "v*" workflow_dispatch: inputs: tag: description: "Git tag to build (e.g. v9.1.1)" required: true add_latest: description: "Also tag this image as latest" type: boolean default: false permissions: contents: read packages: write env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }}-server jobs: build-and-push: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 with: fetch-depth: 0 # needed to fetch tags - name: Determine tag id: tag run: | if [ "${{ github.event_name }}" = "push" ]; then echo "tag=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT else echo "tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT git checkout "refs/tags/${{ inputs.tag }}" fi - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Build and push image uses: docker/build-push-action@v5 with: context: . file: Dockerfile push: true tags: | ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.tag.outputs.tag }} ${{ github.event_name == 'push' && format('{0}/{1}:latest', env.REGISTRY, env.IMAGE_NAME) || '' }} ${{ github.event_name == 'workflow_dispatch' && inputs.add_latest && format('{0}/{1}:latest', env.REGISTRY, env.IMAGE_NAME) || '' }} lemonade-sdk-lemonade-d88f5d9/.github/workflows/claude-review.yml000066400000000000000000000025571515430344400251640ustar00rootroot00000000000000name: Claude Code Review on: pull_request_target: types: [review_requested] issue_comment: types: [created] pull_request_review_comment: types: [created] permissions: contents: read pull-requests: write issues: write id-token: write jobs: claude-review: # Only run for maintainers (OWNER, MEMBER, COLLABORATOR) # Triggers on: @claude mention in comments, or assigning claude as reviewer if: | (github.event_name == 'pull_request_target' && github.event.action == 'review_requested' && github.event.requested_reviewer.login == 'claude[bot]') || ((github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment') && contains(github.event.comment.body, '@claude') && (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR')) runs-on: ubuntu-latest concurrency: group: claude-review-${{ github.event.pull_request.number || github.event.issue.number }} cancel-in-progress: true steps: - uses: actions/checkout@v4 with: fetch-depth: 1 - uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} lemonade-sdk-lemonade-d88f5d9/.github/workflows/cpp_server_build_test_release.yml000066400000000000000000001270731515430344400305170ustar00rootroot00000000000000name: C++ Server Build, Test, and Release 🚀 on: push: branches: ["main"] tags: - v* pull_request: merge_group: workflow_dispatch: inputs: enable_signing: description: 'Enable MSI signing with SignPath (for testing)' required: false default: false type: boolean permissions: contents: write actions: read # Required for SignPath to read workflow/job details env: LEMONADE_DISABLE_SYSTEMD_JOURNAL: "1" jobs: # ======================================================================== # BUILD JOBS - Run on rai-160-sdk workers # ======================================================================== build-lemonade-server-installer: name: Build Lemonade Server Installer runs-on: windows-latest outputs: unsigned-artifact-id: ${{ steps.upload-unsigned-msi.outputs.artifact-id }} steps: - uses: actions/checkout@v4 with: clean: true fetch-depth: 0 - name: Install CMake if not available shell: PowerShell run: | # Check if CMake is already installed $cmakeInstalled = Get-Command cmake -ErrorAction SilentlyContinue if (-not $cmakeInstalled) { Write-Host "CMake not found, installing..." -ForegroundColor Yellow # Download CMake installer $cmakeVersion = "3.28.1" $cmakeUrl = "https://github.com/Kitware/CMake/releases/download/v$cmakeVersion/cmake-$cmakeVersion-windows-x86_64.msi" $cmakeInstaller = "cmake-installer.msi" Invoke-WebRequest -Uri $cmakeUrl -OutFile $cmakeInstaller # Install CMake silently Start-Process msiexec.exe -ArgumentList "/i $cmakeInstaller /quiet /norestart" -Wait # Add CMake to PATH for this session AND future steps $cmakePath = "C:\Program Files\CMake\bin" $env:PATH = "$cmakePath;$env:PATH" # Persist to GITHUB_PATH for future steps echo $cmakePath >> $env:GITHUB_PATH # Verify installation cmake --version if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: CMake installation failed!" -ForegroundColor Red exit 1 } Write-Host "CMake installed successfully and added to PATH!" -ForegroundColor Green } else { Write-Host "CMake is already installed:" -ForegroundColor Green cmake --version } - name: Install WiX Toolset 5.0.2 (CLI) shell: PowerShell run: | $ErrorActionPreference = "Stop" Write-Host "Downloading WiX Toolset 5.0.2 CLI..." -ForegroundColor Cyan $wixUri = "https://github.com/wixtoolset/wix/releases/download/v5.0.2/wix-cli-x64.msi" $msiPath = "$env:RUNNER_TEMP\wix-cli-x64.msi" Invoke-WebRequest -UseBasicParsing -Uri $wixUri -OutFile $msiPath Write-Host "Installing WiX Toolset 5.0.2 CLI..." -ForegroundColor Cyan $p = Start-Process "msiexec.exe" -ArgumentList @("/i", "`"$msiPath`"", "/qn", "/norestart") -PassThru -Wait if ($p.ExitCode -ne 0) { Write-Host "WiX installer exited with code $($p.ExitCode)" -ForegroundColor Red exit $p.ExitCode } - name: Verify WiX installation shell: PowerShell run: | $ErrorActionPreference = "Stop" # WiX CLI MSI does not always add itself to PATH in non-interactive installs, # so we locate it explicitly and then update PATH for subsequent steps. $wixDirs = @( "C:\Program Files\WiX Toolset v5.0\bin", "C:\Program Files (x86)\WiX Toolset v5.0\bin" ) $wixExe = $null foreach ($dir in $wixDirs) { if (Test-Path (Join-Path $dir "wix.exe")) { $wixExe = Join-Path $dir "wix.exe" break } } if (-not $wixExe) { Write-Host "ERROR: wix.exe not found after installation." -ForegroundColor Red Get-ChildItem -Recurse "C:\Program Files" -Filter wix.exe -ErrorAction SilentlyContinue | Select-Object -First 20 | Format-List FullName exit 1 } $wixDir = Split-Path $wixExe -Parent # Persist wix.exe directory to PATH for all subsequent steps "$wixDir" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append Write-Host "Using WiX from: $wixExe" -ForegroundColor Green & $wixExe --version - name: Build C++ Server with CMake shell: PowerShell run: | $ErrorActionPreference = "Stop" Write-Host "Building lemonade-router and lemonade-server..." -ForegroundColor Cyan # Create build directory if (Test-Path "build") { Remove-Item -Recurse -Force "build" } # Configure with preset cmake --preset windows if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # Build cmake --build build --config Release if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } # Verify binaries exist if (-not (Test-Path "build\Release\lemonade-router.exe")) { Write-Host "ERROR: lemonade-router.exe not found!" -ForegroundColor Red exit 1 } if (-not (Test-Path "build\Release\lemonade-server.exe")) { Write-Host "ERROR: lemonade-server.exe not found!" -ForegroundColor Red exit 1 } if (-not (Test-Path "build\Release\lemonade-tray.exe")) { Write-Host "ERROR: lemonade-tray.exe not found!" -ForegroundColor Red exit 1 } Write-Host "C++ build successful!" -ForegroundColor Green - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Build Electron App shell: PowerShell run: | $ErrorActionPreference = "Stop" Write-Host "Building Electron app via CMake target..." -ForegroundColor Cyan # Build using CMake electron-app target (single source of truth) cmake --build --preset windows --target electron-app if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: Electron app build failed!" -ForegroundColor Red exit $LASTEXITCODE } # Verify the build output exists if (-not (Test-Path "build\app\win-unpacked\Lemonade.exe")) { Write-Host "ERROR: Electron app executable not found!" -ForegroundColor Red exit 1 } Write-Host "Electron app build successful!" -ForegroundColor Green - name: Build Web App shell: PowerShell run: | $ErrorActionPreference = "Stop" Write-Host "Building Web app..." -ForegroundColor Cyan cd src\web-app # Install dependencies Write-Host "Installing npm dependencies..." -ForegroundColor Yellow npm install if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: npm install failed!" -ForegroundColor Red exit $LASTEXITCODE } # Build the Web app Write-Host "Building Web app..." -ForegroundColor Yellow npm run build if ($LASTEXITCODE -ne 0) { Write-Host "ERROR: Web app build failed!" -ForegroundColor Red exit $LASTEXITCODE } # Verify the build output exists if (-not (Test-Path "dist\renderer\index.html")) { Write-Host "ERROR: Web app build output not found!" -ForegroundColor Red exit 1 } Write-Host "Web app build successful!" -ForegroundColor Green # Copy web app output to the location expected by CMake/WiX installer Write-Host "Copying web app to build\resources\web-app..." -ForegroundColor Yellow $targetDir = "..\..\build\resources\web-app" if (-not (Test-Path "..\..\build\resources")) { New-Item -ItemType Directory -Force -Path "..\..\build\resources" | Out-Null } if (Test-Path $targetDir) { Remove-Item -Recurse -Force $targetDir } Copy-Item -Path "dist\renderer" -Destination $targetDir -Recurse -Force # Verify the copy succeeded if (-not (Test-Path "..\..\build\resources\web-app\index.html")) { Write-Host "ERROR: Failed to copy web app to build directory!" -ForegroundColor Red exit 1 } Write-Host "Web app copied to build directory successfully!" -ForegroundColor Green - name: Build the Lemonade Server Installer shell: PowerShell run: | $ErrorActionPreference = "Stop" # Run the build installer script .\src\cpp\build_installer.ps1 # Verify installers were created if (-not (Test-Path "lemonade-server-minimal.msi")) { Write-Host "ERROR: Minimal installer not created!" -ForegroundColor Red exit 1 } if (-not (Test-Path "lemonade.msi")) { Write-Host "ERROR: Full installer not created!" -ForegroundColor Red exit 1 } Write-Host "Installers created successfully!" -ForegroundColor Green - name: Upload Lemonade Server Installers id: upload-unsigned-msi uses: actions/upload-artifact@v4 with: name: Lemonade_Server_MSI path: | lemonade-server-minimal.msi lemonade.msi retention-days: 7 sign-msi-installers: name: Sign MSI Installers with SignPath runs-on: windows-latest needs: build-lemonade-server-installer # Sign on tag pushes (releases) or when manually enabled via workflow_dispatch if: startsWith(github.ref, 'refs/tags/v') || inputs.enable_signing == true steps: - name: Sign MSI Installers with SignPath id: sign-msi uses: signpath/github-action-submit-signing-request@v2 with: api-token: '${{ secrets.SIGNPATH_API_TOKEN }}' organization-id: '8103545b-7814-4edc-86d6-a91dc2a2291b' project-slug: 'lemonade' signing-policy-slug: 'release-signing' github-artifact-id: '${{ needs.build-lemonade-server-installer.outputs.unsigned-artifact-id }}' wait-for-completion: true wait-for-completion-timeout-in-seconds: 3600 output-artifact-directory: 'signed-msi' parameters: | version: "${{ startsWith(github.ref, 'refs/tags/') && github.ref_name || 'test' }}" - name: Verify Signed MSI Files shell: PowerShell run: | Write-Host "Verifying signed MSI files..." -ForegroundColor Cyan if (-not (Test-Path "signed-msi\lemonade-server-minimal.msi")) { Write-Host "ERROR: Signed lemonade-server-minimal.msi not found!" -ForegroundColor Red exit 1 } if (-not (Test-Path "signed-msi\lemonade.msi")) { Write-Host "ERROR: Signed lemonade.msi not found!" -ForegroundColor Red exit 1 } Write-Host "Signed MSI files verified!" -ForegroundColor Green Get-ChildItem -Path "signed-msi" -Recurse | Format-Table Name, Length - name: Upload Signed MSI Installers uses: actions/upload-artifact@v4 with: name: Lemonade_Server_MSI_Signed path: | signed-msi/lemonade-server-minimal.msi signed-msi/lemonade.msi retention-days: 7 build-lemonade-deb: name: Build Lemonade .deb Package runs-on: ubuntu-latest outputs: version: ${{ steps.get_version.outputs.version }} steps: - uses: actions/checkout@v4 with: clean: true fetch-depth: 0 - name: Get version from CMakeLists.txt id: get_version uses: ./.github/actions/get-version - name: Build Linux .deb package uses: ./.github/actions/build-linux-deb - name: Upload .deb package uses: actions/upload-artifact@v4 with: name: lemonade-deb path: build/lemonade-server_${{ env.LEMONADE_VERSION }}_amd64.deb retention-days: 7 build-lemonade-rpm: name: Build Lemonade .rpm Package runs-on: ubuntu-latest container: image: fedora:latest outputs: version: ${{ steps.get_version.outputs.version }} steps: - uses: actions/checkout@v4 with: clean: true fetch-depth: 0 - name: Install RPM packaging tools shell: bash run: | set -e dnf install -y rpm-build - name: Get version from CMakeLists.txt id: get_version uses: ./.github/actions/get-version - name: Build Linux .rpm package shell: bash run: | set -e echo "Running setup.sh to configure build environment..." bash setup.sh echo "Building lemonade-router and lemonade-server for Fedora..." cmake --build --preset default RPM_FILE="lemonade-server-${LEMONADE_VERSION}.x86_64.rpm" cd build echo "Creating .rpm package with CPack..." cpack -G RPM -V if [ ! -f "$RPM_FILE" ]; then echo "ERROR: .rpm package not created!" echo "Contents of build directory:" ls -lR . exit 1 fi echo "Package information:" rpm -qip "$RPM_FILE" - name: Upload .rpm package uses: actions/upload-artifact@v4 with: name: lemonade-rpm path: build/lemonade-server-${{ env.LEMONADE_VERSION }}.x86_64.rpm retention-days: 7 build-lemonade-macos-dmg: name: Build Lemonade macOS .dmg (with Electron App) runs-on: macos-latest outputs: version: ${{ steps.get_version.outputs.version }} has_signing: ${{ steps.check_signing.outputs.has_signing }} steps: - uses: actions/checkout@v4 with: clean: true fetch-depth: 0 - name: Get version from CMakeLists.txt id: get_version uses: ./.github/actions/get-version - name: Check signing secrets id: check_signing shell: bash env: APP_CONNECT_KEY: ${{ secrets.MACOS_APP_CONNECT_KEY_GERAMY }} run: | if [ -n "$APP_CONNECT_KEY" ]; then echo "has_signing=true" >> $GITHUB_OUTPUT echo "Signing secrets available - will build signed .pkg" else echo "has_signing=false" >> $GITHUB_OUTPUT echo "No signing secrets - will build and test locally" fi - name: Setup macOS Keychain if: steps.check_signing.outputs.has_signing == 'true' uses: ./.github/actions/setup-macos-keychain with: dev-signing-key: ${{ secrets.MACOS_DEV_SIGNING_IDENTITY_KEY }} inst-signing-key: ${{ secrets.MACOS_INST_SIGNING_IDENTITY_KEY }} certificate-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} api-key: ${{ secrets.MACOS_APP_CONNECT_KEY_GERAMY }} api-key-id: '3WFZZ8F948' api-issuer-id: '2e545619-8206-4d14-9ba9-ef23eff841b2' - name: Build macOS .dmg uses: ./.github/actions/build-macos-dmg with: include-electron: 'true' skip-packaging: ${{ steps.check_signing.outputs.has_signing != 'true' }} - name: Upload .pkg package if: steps.check_signing.outputs.has_signing == 'true' uses: actions/upload-artifact@v4 with: name: lemonade-macos-pkg path: build/*.pkg retention-days: 7 # ---- Unsigned local install + test path ---- - name: Install binaries locally (unsigned path) if: steps.check_signing.outputs.has_signing != 'true' shell: bash run: | set -e echo "Installing binaries locally (no signing)..." # Copy binaries to /usr/local/bin sudo cp build/lemonade-router /usr/local/bin/ sudo cp build/lemonade-server /usr/local/bin/ sudo chmod 755 /usr/local/bin/lemonade-router sudo chmod 755 /usr/local/bin/lemonade-server # Copy resources sudo mkdir -p "/Library/Application Support/Lemonade/resources" if [ -d "build/resources" ]; then sudo cp -R build/resources/* "/Library/Application Support/Lemonade/resources/" 2>/dev/null || true fi # Run the postinst script for directory setup echo "Running post-install script..." sudo bash src/cpp/postinst-full-mac "" "/" # Verify installation echo "Verifying installation..." /usr/local/bin/lemonade-server --version /usr/local/bin/lemonade-router --version echo "Local installation complete!" - name: Stop services after local install (unsigned path) if: steps.check_signing.outputs.has_signing != 'true' shell: bash run: | echo "Stopping services started by postinst..." sudo launchctl bootout system/com.lemonade.server 2>/dev/null || true CURRENT_UID=$(id -u) launchctl bootout gui/"$CURRENT_UID"/com.lemonade.tray 2>/dev/null || true sleep 2 # Clean up PID and lock files sudo rm -f /tmp/lemonade-router.pid sudo rm -f /tmp/lemonade_Router.lock sudo rm -f /tmp/lemonade_Server.lock sudo rm -f /tmp/lemonade_Tray.lock sudo rm -f /tmp/lemonade*.pid /tmp/lemonade*.lock echo "Services stopped and cleaned up." - name: Setup Python and virtual environment (unsigned path) if: steps.check_signing.outputs.has_signing != 'true' uses: ./.github/actions/setup-venv with: venv-name: '.venv' python-version: '3.10' requirements-file: 'test/requirements.txt' - name: Run CLI tests (unsigned path) if: steps.check_signing.outputs.has_signing != 'true' shell: bash env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} HF_HOME: ${{ github.workspace }}/hf-cache run: | set -e echo "Running CLI tests..." .venv/bin/python test/server_cli.py --server-binary /usr/local/bin/lemonade-server .venv/bin/python test/server_cli.py --server-binary /usr/local/bin/lemonade-server --ephemeral .venv/bin/python test/server_cli.py --server-binary /usr/local/bin/lemonade-server --listen-all .venv/bin/python test/server_cli.py --server-binary /usr/local/bin/lemonade-server --api-key echo "CLI tests PASSED!" - name: Run endpoint tests (unsigned path) if: steps.check_signing.outputs.has_signing != 'true' shell: bash env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} HF_HOME: ${{ github.workspace }}/hf-cache run: | set -e echo "Running endpoint tests..." .venv/bin/python test/server_endpoints.py --server-binary /usr/local/bin/lemonade-server .venv/bin/python test/server_endpoints.py --server-binary /usr/local/bin/lemonade-server --server-per-test echo "Endpoint tests PASSED!" - name: Run Ollama API tests (unsigned path) if: steps.check_signing.outputs.has_signing != 'true' shell: bash env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} HF_HOME: ${{ github.workspace }}/hf-cache run: | set -e echo "Running Ollama API tests..." .venv/bin/python test/test_ollama.py --server-binary /usr/local/bin/lemonade-server echo "Ollama API tests PASSED!" - name: Cleanup keychain if: always() && steps.check_signing.outputs.has_signing == 'true' shell: bash run: | if [ -n "$SIGNING_KEYCHAIN_PATH" ] && [ -f "$SIGNING_KEYCHAIN_PATH" ]; then echo "Cleaning up temporary keychain..." security delete-keychain "$SIGNING_KEYCHAIN_PATH" 2>/dev/null || true fi build-lemonade-appimage: name: Build Lemonade AppImage runs-on: ubuntu-latest outputs: version: ${{ steps.get_version.outputs.version }} steps: - uses: actions/checkout@v4 with: clean: true fetch-depth: 0 - name: Get version from CMakeLists.txt id: get_version uses: ./.github/actions/get-version - name: Run setup.sh to configure environment shell: bash run: | set -e echo "Running setup.sh to configure build environment..." bash setup.sh echo "Build environment configured successfully!" - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Build AppImage shell: bash run: | set -e echo "Building Lemonade AppImage..." cmake --build --preset default --target appimage # Verify AppImage was created APPIMAGE_FILE="build/app-appimage/Lemonade-${LEMONADE_VERSION}-x86_64.AppImage" if [ ! -f "$APPIMAGE_FILE" ]; then echo "ERROR: AppImage not created!" echo "Contents of build/app-appimage directory:" ls -lh build/app-appimage/ || echo "Directory does not exist" exit 1 fi echo "AppImage created successfully!" ls -lh "$APPIMAGE_FILE" # Verify it's executable chmod +x "$APPIMAGE_FILE" "$APPIMAGE_FILE" --appimage-help | head -5 - name: Upload AppImage artifact uses: actions/upload-artifact@v4 with: name: lemonade-appimage path: build/app-appimage/Lemonade-${{ env.LEMONADE_VERSION }}-x86_64.AppImage retention-days: 7 # ======================================================================== # TEST JOBS - Inference tests on self-hosted runners # ======================================================================== test-exe-inference: name: Test .exe - ${{ matrix.name }} runs-on: ${{ matrix.runner }} needs: build-lemonade-server-installer # Skip inference tests when signing is enabled (tag pushes or manual workflow_dispatch) if: ${{ !startsWith(github.ref, 'refs/tags/') && inputs.enable_signing != true }} strategy: fail-fast: false matrix: include: - name: llamacpp script: server_llm.py extra_args: "--wrapped-server llamacpp" backends: "vulkan rocm" runner: [rai300_400, Windows] - name: ryzenai script: server_llm.py extra_args: "--wrapped-server ryzenai" backends: "cpu hybrid npu" runner: [rai300_400, Windows] - name: flm script: server_llm.py extra_args: "--wrapped-server flm" backends: "npu" runner: [rai300_400, Windows] - name: whisper script: server_whisper.py extra_args: "--wrapped-server whispercpp" backends: "cpu npu" runner: [rai300_400, Windows] - name: flm-whisper script: server_whisper.py extra_args: "--wrapped-server flm" backends: "npu" runner: [rai300_400, Windows] - name: stable-diffusion script: server_sd.py extra_args: "" backends: "cpu" runner: [rai300_400, Windows] - name: text-to-speech script: server_tts.py extra_args: "" backends: "" runner: [rai300_400, Windows] - name: stable-diffusion (stx-halo) script: server_sd.py extra_args: "" backends: "rocm" runner: [stx-halo, Windows] env: LEMONADE_CI_MODE: "True" LEMONADE_CACHE_DIR: ".\\ci-cache" PYTHONIOENCODING: utf-8 GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} steps: - uses: actions/checkout@v4 - name: Cleanup processes uses: ./.github/actions/cleanup-processes-windows - name: Set environment variables shell: PowerShell run: | $cwd = (Get-Item .\).FullName echo "HF_HOME=$cwd\hf-cache" >> $Env:GITHUB_ENV echo "LEMONADE_INSTALL_PATH=$cwd\lemonade_server_install" >> $Env:GITHUB_ENV - name: Install and Verify Lemonade Server uses: ./.github/actions/install-lemonade-server-msi with: install-path: ${{ env.LEMONADE_INSTALL_PATH }} - name: Setup Python and virtual environment uses: ./.github/actions/setup-venv with: venv-name: '.venv' python-version: '3.10' requirements-file: 'test/requirements.txt' - name: Install FLM backend for FLM wrapped-server tests if: ${{ runner.os == 'Windows' && contains(matrix.extra_args, '--wrapped-server flm') }} shell: PowerShell run: | $ErrorActionPreference = "Stop" $serverExe = Join-Path $env:LEMONADE_INSTALL_PATH "bin\lemonade-server.exe" Write-Host "Installing FLM backend for CI inference tests..." -ForegroundColor Cyan & $serverExe recipes --install flm:npu if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Run tests shell: PowerShell env: HF_HOME: ${{ env.HF_HOME }} run: | $ErrorActionPreference = "Stop" $venvPython = ".\.venv\Scripts\python.exe" $serverExe = Join-Path $env:LEMONADE_INSTALL_PATH "bin\lemonade-server.exe" $extraArgs = "${{ matrix.extra_args }}" -split " " | Where-Object { $_ } $backends = "${{ matrix.backends }}" -split " " | Where-Object { $_ } if ($backends.Count -eq 0) { Write-Host "Running test/${{ matrix.script }} ${{ matrix.extra_args }}" -ForegroundColor Cyan & $venvPython test/${{ matrix.script }} @extraArgs --server-binary $serverExe if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } else { foreach ($backend in $backends) { Write-Host "Running test/${{ matrix.script }} ${{ matrix.extra_args }} --backend $backend" -ForegroundColor Cyan & $venvPython test/${{ matrix.script }} @extraArgs --backend $backend --server-binary $serverExe if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } } } - name: Cleanup if: always() uses: ./.github/actions/cleanup-processes-windows test-deb-inference: name: Test .deb - ${{ matrix.name }} runs-on: [rai300_400, Linux] needs: build-lemonade-deb # Skip inference tests when signing is enabled (tag pushes or manual workflow_dispatch) if: ${{ !startsWith(github.ref, 'refs/tags/') && inputs.enable_signing != true }} strategy: fail-fast: false matrix: include: - name: llamacpp script: server_llm.py extra_args: "--wrapped-server llamacpp" backends: "vulkan rocm" - name: stable-diffusion script: server_sd.py extra_args: "" backends: "cpu rocm" - name: whisper script: server_whisper.py extra_args: "--wrapped-server whispercpp" backends: "cpu vulkan" - name: flm script: server_llm.py extra_args: "--wrapped-server flm" backends: "npu" - name: text-to-speech script: server_tts.py extra_args: "" backends: "" env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} LEMONADE_VERSION: ${{ needs.build-lemonade-deb.outputs.version }} steps: - uses: actions/checkout@v4 - name: Cleanup processes uses: ./.github/actions/cleanup-processes-linux - name: Set HF_HOME environment variable run: echo "HF_HOME=$PWD/hf-cache" >> $GITHUB_ENV - name: Install Lemonade (.deb) uses: ./.github/actions/install-lemonade-deb with: version: ${{ env.LEMONADE_VERSION }} - name: Setup Python and virtual environment uses: ./.github/actions/setup-venv with: venv-name: '.venv' python-version: '3.10' requirements-file: 'test/requirements.txt' - name: Run tests env: HF_HOME: ${{ env.HF_HOME }} run: | set -e if [ -z "${{ matrix.backends }}" ]; then echo "Running test/${{ matrix.script }} ${{ matrix.extra_args }}" .venv/bin/python test/${{ matrix.script }} ${{ matrix.extra_args }} --server-binary lemonade-server else for backend in ${{ matrix.backends }}; do echo "Running test/${{ matrix.script }} ${{ matrix.extra_args }} --backend $backend" .venv/bin/python test/${{ matrix.script }} ${{ matrix.extra_args }} --backend $backend --server-binary lemonade-server done fi - name: Print server logs on failure if: failure() run: | # Resolve runtime dir: $XDG_RUNTIME_DIR/lemonade when available, else /tmp LEMON_RUNTIME_DIR="${XDG_RUNTIME_DIR:+${XDG_RUNTIME_DIR}/lemonade}" LEMON_RUNTIME_DIR="${LEMON_RUNTIME_DIR:-/tmp}" echo "=== Server Log (if exists) ===" LOG_FILE="${LEMON_RUNTIME_DIR}/lemonade-server.log" # Also check /tmp as a safety net when XDG was active but log went to fallback if [ ! -f "$LOG_FILE" ]; then LOG_FILE="/tmp/lemonade-server.log"; fi if [ -f "$LOG_FILE" ]; then echo "Last 100 lines of ${LOG_FILE}:" tail -100 "$LOG_FILE" else echo "Log file not found (checked ${LEMON_RUNTIME_DIR} and /tmp)" fi echo "" echo "=== Router PID file (if exists) ===" PID_FILE="${LEMON_RUNTIME_DIR}/lemonade-router.pid" if [ ! -f "$PID_FILE" ]; then PID_FILE="/tmp/lemonade-router.pid"; fi if [ -f "$PID_FILE" ]; then cat "$PID_FILE" else echo "PID file not found" fi echo "" echo "=== Lock file status ===" ls -la "${LEMON_RUNTIME_DIR}"/lemonade*.lock 2>/dev/null || true ls -la /tmp/lemonade*.lock 2>/dev/null || echo "No lock files found" - name: Cleanup if: always() uses: ./.github/actions/cleanup-processes-linux test-rpm-package: name: Test .rpm - Fedora runs-on: ubuntu-latest needs: build-lemonade-rpm container: image: fedora:latest env: LEMONADE_VERSION: ${{ needs.build-lemonade-rpm.outputs.version }} steps: - name: Download Lemonade .rpm Package uses: actions/download-artifact@v4 with: name: lemonade-rpm path: . - name: Install and verify Lemonade (.rpm) shell: bash run: | set -e RPM_FILE="lemonade-server-${LEMONADE_VERSION}.x86_64.rpm" if [ ! -f "$RPM_FILE" ]; then echo "ERROR: .rpm file not found: $RPM_FILE" ls -la *.rpm 2>/dev/null || echo "No .rpm files found in current directory" exit 1 fi dnf install -y shadow-utils "$RPM_FILE" echo "Installed package information:" rpm -qi lemonade-server echo "Installed file list:" rpm -ql lemonade-server | sort test -f /etc/lemonade/lemonade.conf test -f /opt/bin/lemonade-server test -f /opt/bin/lemonade-router /opt/bin/lemonade-server --version /opt/bin/lemonade-router --version test-dmg-inference: name: Test .dmg - llamacpp (metal) runs-on: macos-latest needs: build-lemonade-macos-dmg # Skip inference tests when signing is enabled (tag pushes or manual workflow_dispatch) # Also skip when no signing secrets (tests already ran inline in build job) if: ${{ needs.build-lemonade-macos-dmg.outputs.has_signing == 'true' && !startsWith(github.ref, 'refs/tags/') && inputs.enable_signing != true }} env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} LEMONADE_VERSION: ${{ needs.build-lemonade-macos-dmg.outputs.version }} steps: - uses: actions/checkout@v4 - name: Set HF_HOME environment variable run: echo "HF_HOME=$PWD/hf-cache" >> $GITHUB_ENV - name: Install Lemonade Server (.pkg) uses: ./.github/actions/install-lemonade-server-dmg with: version: ${{ env.LEMONADE_VERSION }} - name: Setup Python and virtual environment uses: ./.github/actions/setup-venv with: venv-name: '.venv' python-version: '3.10' requirements-file: 'test/requirements.txt' - name: Test llamacpp (metal) env: HF_HOME: ${{ env.HF_HOME }} run: | set -e .venv/bin/python test/server_llm.py --wrapped-server llamacpp --backend metal --server-binary /usr/local/bin/lemonade-server - name: Print server logs on failure if: failure() run: | echo "=== Server Log (if exists) ===" if [ -f /var/log/lemonade/lemonade-server.out.log ]; then echo "Last 100 lines of lemonade-server.out.log:" tail -100 /var/log/lemonade/lemonade-server.out.log fi if [ -f /var/log/lemonade/lemonade-server.err.log ]; then echo "Last 100 lines of lemonade-server.err.log:" tail -100 /var/log/lemonade/lemonade-server.err.log fi echo "" echo "=== Temp logs ===" for f in /tmp/lemonade*.log; do if [ -f "$f" ]; then echo "--- $f ---" tail -50 "$f" fi done 2>/dev/null || echo "No temp log files found" echo "" echo "=== Lock file status ===" ls -la /tmp/lemonade*.lock 2>/dev/null || echo "No lock files found" # ======================================================================== # CLI AND ENDPOINTS TESTS - Run on GitHub-hosted runners (no GPU needed) # ======================================================================== test-cli-endpoints: name: Test ${{ matrix.test_type }} (${{ matrix.os }}) runs-on: ${{ matrix.os }} needs: - build-lemonade-server-installer - build-lemonade-deb - build-lemonade-macos-dmg strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] test_type: [cli, endpoints, system-info, ollama, llamacpp-system] exclude: - os: macos-latest test_type: system-info env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} LEMONADE_VERSION: ${{ needs.build-lemonade-deb.outputs.version }} steps: - uses: actions/checkout@v4 # ---- Linux Setup ---- - name: Download .deb package if: runner.os == 'Linux' uses: actions/download-artifact@v4 with: name: lemonade-deb path: . - name: Install Lemonade (.deb) if: runner.os == 'Linux' shell: bash run: | DEB_FILE="lemonade-server_${{ env.LEMONADE_VERSION }}_amd64.deb" echo "Installing .deb package: $DEB_FILE" sudo apt install ./"$DEB_FILE" # Verify binaries installed lemonade-server --version # Verify the systemd service started automatically sudo systemctl status lemonade-server - name: Set environment (Linux) if: runner.os == 'Linux' shell: bash run: | echo "HF_HOME=$PWD/hf-cache" >> $GITHUB_ENV echo "VENV_PYTHON=.venv/bin/python" >> $GITHUB_ENV echo "SERVER_BINARY=lemonade-server" >> $GITHUB_ENV # ---- Windows Setup ---- - name: Setup (Windows) if: runner.os == 'Windows' shell: pwsh run: | $cwd = (Get-Item .).FullName echo "HF_HOME=$cwd\hf-cache" >> $Env:GITHUB_ENV echo "LEMONADE_INSTALL_PATH=$cwd\lemonade_server_install" >> $Env:GITHUB_ENV - name: Install Lemonade Server (Windows) if: runner.os == 'Windows' uses: ./.github/actions/install-lemonade-server-msi with: install-path: ${{ env.LEMONADE_INSTALL_PATH }} - name: Set paths (Windows) if: runner.os == 'Windows' shell: pwsh run: | echo "VENV_PYTHON=.venv/Scripts/python.exe" >> $Env:GITHUB_ENV echo "SERVER_BINARY=$Env:LEMONADE_INSTALL_PATH\bin\lemonade-server.exe" >> $Env:GITHUB_ENV # ---- macOS Setup (only when signed .pkg is available) ---- - name: Download .pkg package if: runner.os == 'macOS' && needs.build-lemonade-macos-dmg.outputs.has_signing == 'true' uses: actions/download-artifact@v4 with: name: lemonade-macos-pkg path: . - name: Install Lemonade Server (.pkg) id: install-pkg if: runner.os == 'macOS' && needs.build-lemonade-macos-dmg.outputs.has_signing == 'true' uses: ./.github/actions/install-lemonade-server-dmg with: version: ${{ env.LEMONADE_VERSION }} download-artifact: 'false' - name: Set environment (macOS) if: runner.os == 'macOS' && needs.build-lemonade-macos-dmg.outputs.has_signing == 'true' shell: bash run: | echo "HF_HOME=$PWD/hf-cache" >> $GITHUB_ENV echo "VENV_PYTHON=.venv/bin/python" >> $GITHUB_ENV echo "SERVER_BINARY=${{ steps.install-pkg.outputs.bin-path }}/lemonade-server" >> $GITHUB_ENV # ---- Common Setup ---- - name: Setup Python and virtual environment uses: ./.github/actions/setup-venv with: venv-name: '.venv' python-version: '3.10' requirements-file: 'test/requirements.txt' # ---- Run Tests ---- - name: Stop systemd service before tests (Linux) if: runner.os == 'Linux' shell: bash run: | echo "Stopping lemonade-server systemd service..." sudo systemctl stop lemonade-server || true sudo systemctl status lemonade-server || true - name: Stop LaunchDaemon before tests (macOS) if: runner.os == 'macOS' && needs.build-lemonade-macos-dmg.outputs.has_signing == 'true' shell: bash run: | echo "Stopping lemonade-server LaunchDaemon..." sudo launchctl bootout system/com.lemonade.server 2>/dev/null || true CURRENT_UID=$(id -u) launchctl bootout gui/"$CURRENT_UID"/com.lemonade.tray 2>/dev/null || true sleep 2 # Clean up PID and lock files left behind by killed processes echo "Cleaning up stale PID and lock files..." sudo rm -f /tmp/lemonade-router.pid sudo rm -f /tmp/lemonade_Router.lock sudo rm -f /tmp/lemonade_Server.lock sudo rm -f /tmp/lemonade_Tray.lock sudo rm -f /tmp/lemonade*.pid /tmp/lemonade*.lock echo "LaunchDaemon stopped and cleaned up." - name: Run ${{ matrix.test_type }} tests if: ${{ !(runner.os == 'macOS' && needs.build-lemonade-macos-dmg.outputs.has_signing != 'true') }} shell: bash env: HF_HOME: ${{ env.HF_HOME }} run: | set -e # Exit on error if [ "${{ matrix.test_type }}" = "cli" ]; then echo "Running CLI tests..." $VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY" $VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY" --ephemeral $VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY" --listen-all $VENV_PYTHON test/server_cli.py --server-binary "$SERVER_BINARY" --api-key elif [ "${{ matrix.test_type }}" = "endpoints" ]; then echo "Running endpoint tests..." $VENV_PYTHON test/server_endpoints.py --server-binary "$SERVER_BINARY" $VENV_PYTHON test/server_endpoints.py --server-binary "$SERVER_BINARY" --server-per-test elif [ "${{ matrix.test_type }}" = "system-info" ]; then echo "Running system-info mock hardware tests..." # Unset LEMONADE_CI_MODE so the server uses mock cache files unset LEMONADE_CI_MODE $VENV_PYTHON test/server_system_info.py --server-binary "$SERVER_BINARY" elif [ "${{ matrix.test_type }}" = "ollama" ]; then echo "Running Ollama API tests..." $VENV_PYTHON test/test_ollama.py --server-binary "$SERVER_BINARY" elif [ "${{ matrix.test_type }}" = "llamacpp-system" ]; then echo "Running LlamaCpp System Backend tests..." $VENV_PYTHON test/test_llamacpp_system_backend.py --server-binary "$SERVER_BINARY" fi echo "${{ matrix.test_type }} tests PASSED!" # ======================================================================== # RELEASE JOB - Add artifacts to GitHub release # ======================================================================== release: name: Create GitHub Release runs-on: ubuntu-latest needs: - sign-msi-installers - build-lemonade-deb - build-lemonade-rpm - build-lemonade-macos-dmg - build-lemonade-appimage - test-cli-endpoints - test-rpm-package if: startsWith(github.ref, 'refs/tags/v') env: LEMONADE_VERSION: ${{ needs.build-lemonade-deb.outputs.version }} steps: - name: Checkout for release notes action uses: actions/checkout@v4 with: sparse-checkout: .github - name: Download Signed Lemonade Server Installer (Windows) uses: actions/download-artifact@v4 with: name: Lemonade_Server_MSI_Signed path: . - name: Download Lemonade .deb Package uses: actions/download-artifact@v4 with: name: lemonade-deb path: . - name: Download Lemonade .rpm Package uses: actions/download-artifact@v4 with: name: lemonade-rpm path: . - name: Download Lemonade macOS .pkg Package uses: actions/download-artifact@v4 with: name: lemonade-macos-pkg path: . - name: Download Lemonade AppImage (Linux) uses: actions/download-artifact@v4 with: name: lemonade-appimage path: . - name: Verify release artifacts run: | echo "Release artifacts:" ls -lh lemonade-server-minimal.msi ls -lh lemonade.msi ls -lh lemonade-server_${LEMONADE_VERSION}_amd64.deb ls -lh lemonade-server-${LEMONADE_VERSION}.x86_64.rpm ls -lh *.pkg ls -lh Lemonade-${LEMONADE_VERSION}-x86_64.AppImage - name: Generate release notes id: release-notes uses: ./.github/actions/generate-release-notes with: version: ${{ env.LEMONADE_VERSION }} repo: ${{ github.repository }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Create Release uses: softprops/action-gh-release@v2 with: name: ${{ github.ref_name }} body_path: ${{ steps.release-notes.outputs.release_notes_file }} files: | lemonade-server-minimal.msi lemonade.msi lemonade-server_${{ env.LEMONADE_VERSION }}_amd64.deb lemonade-server-${{ env.LEMONADE_VERSION }}.x86_64.rpm *.pkg Lemonade-${{ env.LEMONADE_VERSION }}-x86_64.AppImage fail_on_unmatched_files: true lemonade-sdk-lemonade-d88f5d9/.github/workflows/docker-build-smoke-test.yml000066400000000000000000000032521515430344400270560ustar00rootroot00000000000000name: Docker Build Smoke Test on: pull_request: permissions: contents: read jobs: docker-smoke-test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Build Docker image run: | docker build -t lemonade:test . - name: Run container run: | docker run -d \ --name lemonade-test \ -p 8000:8000 \ lemonade:test - name: Wait for server to be ready run: | echo "Waiting for Lemonade to start..." for i in {1..30}; do if curl -sf http://localhost:8000/live > /dev/null; then echo "Server is up" exit 0 fi sleep 2 done echo "Server did not start in time" docker logs lemonade-test exit 1 - name: Load model run: | curl -X POST http://localhost:8000/api/v1/load \ -H "Content-Type: application/json" \ -d '{"model_name": "Qwen3-0.6B-GGUF"}' - name: Chat completion smoke test run: | RESPONSE=$(curl -s http://localhost:8000/api/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen3-0.6B-GGUF", "messages": [ { "role": "user", "content": "Hello, Lemonade!" } ] }') echo "Response:" echo "$RESPONSE" echo "$RESPONSE" | grep -q '"choices"' || exit 1 - name: Cleanup if: always() run: | docker logs lemonade-test || true docker rm -f lemonade-test || true lemonade-sdk-lemonade-d88f5d9/.github/workflows/docs_and_style.yml000066400000000000000000000017611515430344400254160ustar00rootroot00000000000000name: Docs And Style on: push: branches: ["main"] pull_request: merge_group: permissions: contents: read jobs: markdown-link-check: runs-on: ubuntu-latest concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true steps: - uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' - name: Install markdown-link-check run: npm install -g markdown-link-check - name: Check Markdown links shell: bash run: | # To run this manually on Windows, run: # npm install -g markdown-link-check # Get-ChildItem -Recurse -Filter *.md | ForEach-Object { markdown-link-check $_.FullName -c .\.github\workflows\mlc_config.json } set -euo pipefail find . -type f -name "*.md" -not -path "./node_modules/*" \ -exec markdown-link-check -c .github/workflows/mlc_config.json {} \; lemonade-sdk-lemonade-d88f5d9/.github/workflows/linux_distro_builds.yml000066400000000000000000000077451515430344400265210ustar00rootroot00000000000000name: Linux Distro Builds 🐧 on: push: branches: ["main"] pull_request: merge_group: workflow_dispatch: permissions: contents: read jobs: build-linux: name: Build on ${{ matrix.distro }} runs-on: ubuntu-latest container: image: ${{ matrix.image }} strategy: fail-fast: false matrix: include: - distro: Arch image: archlinux:latest python: python - distro: Debian image: debian:trixie python: python3 - distro: Fedora image: fedora:latest python: python3 steps: - name: Checkout code uses: actions/checkout@v4 with: clean: true fetch-depth: 0 - name: Run setup.sh to configure environment run: | set -e echo "Running setup.sh for ${{ matrix.distro }}..." bash setup.sh echo "Verifying process management tools..." command -v pgrep && command -v pkill && command -v ps echo "Verifying SSL certificates..." ls -la /etc/ssl/certs/ | head -10 echo "Testing HTTPS connectivity..." curl -sI https://huggingface.co | head -5 || echo "Warning: HTTPS test failed" echo "Setup completed successfully!" - name: Build C++ Server with CMake run: | set -e echo "Building lemonade-router and lemonade-server on ${{ matrix.distro }}..." # Build echo "Building binaries..." cmake --build --preset default # Verify binaries exist if [ ! -f "build/lemonade-router" ]; then echo "ERROR: lemonade-router not found!" echo "Build directory contents:" ls -lh build/ exit 1 fi if [ ! -f "build/lemonade-server" ]; then echo "ERROR: lemonade-server not found!" echo "Build directory contents:" ls -lh build/ exit 1 fi echo "Binaries found successfully" ls -lh build/lemonade-router build/lemonade-server echo "Verifying binary executability..." ./build/lemonade-router --version ./build/lemonade-server --version echo "${{ matrix.distro }} build successful!" - name: Verify system library linking run: | echo "Checking library dependencies..." cd build echo "=== lemonade-router dependencies ===" ldd lemonade-router echo "" echo "=== lemonade-server dependencies ===" ldd lemonade-server echo "" echo "All dependencies resolved successfully on ${{ matrix.distro }}!" - name: Setup Python virtual environment run: | echo "Creating Python virtual environment..." if [ "${{ matrix.distro }}" = "Debian" ]; then apt install -y python3-venv fi ${{ matrix.python }} -m venv .venv echo "Activating virtual environment and installing dependencies..." . .venv/bin/activate pip install --upgrade pip pip install -r test/requirements.txt echo "Python environment setup complete!" - name: Run CLI tests env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 run: | set -e . .venv/bin/activate SERVER_BINARY="$(pwd)/build/lemonade-server" echo "Running CLI tests..." python test/server_cli.py --server-binary "$SERVER_BINARY" echo "CLI tests PASSED!" - name: Run endpoint tests env: LEMONADE_CI_MODE: "True" PYTHONIOENCODING: utf-8 run: | set -e . .venv/bin/activate SERVER_BINARY="$(pwd)/build/lemonade-server" echo "Running endpoint tests..." python test/server_endpoints.py --server-binary "$SERVER_BINARY" echo "Endpoint tests PASSED!" lemonade-sdk-lemonade-d88f5d9/.github/workflows/mlc_config.json000066400000000000000000000003151515430344400246660ustar00rootroot00000000000000{ "ignorePatterns": [ { "pattern": "@amd.com" }, { "pattern": "localhost" }, {"pattern":"^https?://"} ], "timeout": "10s", "retryOn429": true, "retryCount": 3 } lemonade-sdk-lemonade-d88f5d9/.github/workflows/monitor_selfhosted_runners.yml000066400000000000000000000135261515430344400301110ustar00rootroot00000000000000name: Monitor Self-Hosted Runners on: schedule: - cron: "0 1 * * 1" # every Monday at 1AM UTC workflow_dispatch: permissions: actions: read # Required to read artifacts jobs: check: runs-on: ubuntu-latest steps: - name: Get list of runners id: runners run: | # Try to get repo-level runners first curl -s \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/repos/${{ github.repository }}/actions/runners \ > repo_runners.json # Also get org-level runners curl -s \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ https://api.github.com/orgs/${{ github.repository_owner }}/actions/runners \ > org_runners.json # Combine and deduplicate runner names repo_names=$(jq -r '.runners[]?.name // empty' repo_runners.json 2>/dev/null || echo "") org_names=$(jq -r '.runners[]?.name // empty' org_runners.json 2>/dev/null || echo "") # Fallback to known runner list if API fails known_runners="jfowers-stx-01,jfowers-stx-02,sjlab-stx-halo-06,sjlab-stx-halo-07,sjlab-stx-halo-08,sjlab-stx-halo-09,sjlab-stx-halo-10,sjlab-stx-halo-11,sjlab-stx-halo-12,sjlab-stx-halo-13,sjlab-stx-0" if [ -n "$repo_names" ] || [ -n "$org_names" ]; then all_names=$(echo -e "$repo_names\n$org_names" | sort -u | tr '\n' ',' | sed 's/,$//') echo "names=$all_names" >> $GITHUB_OUTPUT echo "Using API discovered runners: $all_names" else echo "names=$known_runners" >> $GITHUB_OUTPUT echo "Using fallback runner list: $known_runners" fi - name: Get artifacts id: artifacts run: | # Fetch all artifacts with pagination echo '{"artifacts":[]}' > artifacts.json page=1 while true; do response=$(curl -s \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ "https://api.github.com/repos/${{ github.repository }}/actions/artifacts?per_page=100&page=$page") # Check if request was successful and has artifacts artifact_count=$(echo "$response" | jq '.artifacts | length') if [ "$artifact_count" -eq 0 ] || [ "$artifact_count" = "null" ]; then break fi # Merge artifacts into our main file jq -s '.[0].artifacts + .[1].artifacts | {"artifacts": .}' artifacts.json <(echo "$response") > temp.json mv temp.json artifacts.json page=$((page + 1)) # Safety break after 10 pages (1000 artifacts) if [ $page -gt 10 ]; then break fi done - name: Check each runner heartbeat id: check run: | missing="" now=$(date -u +%s) echo "Current time: $(date -u)" echo "Total artifacts found: $(jq '.artifacts | length' artifacts.json)" echo "Heartbeat artifacts found: $(jq '[.artifacts[] | select(.name | startswith("heartbeat-"))] | length' artifacts.json)" for r in $(echo "${{ steps.runners.outputs.names }}" | tr ',' ' '); do [ -z "$r" ] && continue echo "Checking $r" ts=$(jq -r --arg r "$r" '[.artifacts[] | select(.name=="heartbeat-"+$r)] | sort_by(.updated_at) | last | .updated_at // empty' artifacts.json) if [ -z "$ts" ] || [ "$ts" == "null" ]; then echo "No heartbeat found for $r" missing="$missing $r" continue fi echo "Latest heartbeat for $r: $ts" hb=$(date -d "$ts" +%s) diff=$((now - hb)) echo "Age: $diff seconds ($(($diff / 3600)) hours)" if [ $diff -gt 691200 ]; then # 8 days (weekly + 1 day buffer) echo "Heartbeat stale for $r (last seen: $ts)" missing="$missing $r" else echo "Heartbeat OK for $r" fi done echo "missing=$missing" >> $GITHUB_OUTPUT - name: Fail if any runner missing if: steps.check.outputs.missing != '' run: | echo "Missing or stale runners: ${{ steps.check.outputs.missing }}" exit 1 - name: Send Teams alert via Power Automate if: failure() run: | echo "Sending Teams alert via Power Automate..." response=$(curl -s -w "%{http_code}" -X POST \ -H "Content-Type: application/json" \ -d '{ "type": "AdaptiveCard", "version": "1.3", "body": [ { "type": "TextBlock", "text": "⚠️ Self-hosted Runner Alert", "weight": "bolder", "size": "medium", "color": "attention" }, { "type": "TextBlock", "text": "The following runners are offline or missing heartbeats:", "wrap": true }, { "type": "TextBlock", "text": "${{ steps.check.outputs.missing }}", "wrap": true, "fontType": "monospace" } ] }' \ "${{ secrets.TEAMS_WEBHOOK_URL }}") http_code="${response: -3}" response_body="${response%???}" echo "HTTP Status: $http_code" echo "Response: $response_body" if [ "$http_code" != "200" ] && [ "$http_code" != "202" ]; then echo "Power Automate webhook failed with status $http_code" else echo "Teams alert sent successfully via Power Automate" fi lemonade-sdk-lemonade-d88f5d9/.github/workflows/publish-website.yml000066400000000000000000000035321515430344400255300ustar00rootroot00000000000000name: Publish Website on: push: branches: ["main"] tags: - v* workflow_dispatch: repository_dispatch: types: [marketplace-updated] jobs: build-n-publish-website: name: Build and publish lemonade-server.ai website runs-on: ubuntu-latest steps: - uses: actions/checkout@main with: fetch-depth: 0 - name: Set up Python uses: actions/setup-python@v5 with: python-version: "3.10" - name: Create virtual environment and install dependencies run: | python -m venv .venv venvPython=".venv/bin/python" venvPip=".venv/bin/pip" $venvPython -m pip install --upgrade pip $venvPip install -r docs/assets/mkdocs_requirements.txt - name: Configure git run: | git config --global user.name "Lemonade Bot" git config --global user.email "lemonade@amd.com" - name: Capture main branch commit hash run: | echo "MAIN_COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - name: Merge into website branch run: | git checkout website git merge origin/main -X theirs - name: Update README with marketplace apps run: | venvPython=".venv/bin/python" $venvPython docs/update_readme_marketplace.py - name: Run mkdocs update script run: | venvPython=".venv/bin/python" $venvPython docs/publish_website_docs.py - name: Push updates run: | git add . if git diff --staged --quiet; then echo "No changes to commit, skipping commit step" else git commit -m "Update website for: ${MAIN_COMMIT_HASH}" fi git push lemonade-sdk-lemonade-d88f5d9/.github/workflows/runner_heartbeat.yml000066400000000000000000000026541515430344400257560ustar00rootroot00000000000000name: Runner Heartbeat on: schedule: - cron: "0 1 * * 0" # every Sunday at 1AM UTC workflow_dispatch: permissions: actions: write # Required to upload artifacts jobs: heartbeat: strategy: matrix: runner: - jfowers-stx-01 - jfowers-stx-02 - sjlab-stx-halo-06 - sjlab-stx-halo-07 - sjlab-stx-halo-08 - sjlab-stx-halo-09 - sjlab-stx-halo-10 - sjlab-stx-halo-11 - sjlab-stx-halo-12 - sjlab-stx-halo-13 - sjlab-stx-0 fail-fast: false # Continue even if one runner fails runs-on: [self-hosted, "${{ matrix.runner }}"] steps: - name: Create heartbeat file (Windows) if: runner.os == 'Windows' shell: powershell run: | $timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK" $runner = "${{ runner.name }}" "$timestamp" | Out-File -FilePath "heartbeat-$runner.txt" -Encoding utf8 - name: Create heartbeat file (Linux) if: runner.os == 'Linux' shell: bash run: | timestamp=$(date -Iseconds) runner="${{ runner.name }}" echo "$timestamp" > "heartbeat-$runner.txt" - name: Upload heartbeat artifact uses: actions/upload-artifact@v4 with: name: heartbeat-${{ runner.name }} path: heartbeat-${{ runner.name }}.txt retention-days: 14 lemonade-sdk-lemonade-d88f5d9/.github/workflows/test-new-runner.yml000066400000000000000000000022171515430344400254760ustar00rootroot00000000000000name: Test New Runner on Windows permissions: contents: read on: workflow_dispatch: inputs: runner: description: 'Runner to use' required: true type: choice options: - test default: 'test' jobs: test-runner: runs-on: ${{ inputs.runner }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Show runner information run: | echo "Hello from the runner!" echo "Runner name: ${{ runner.name }}" echo "Runner OS: ${{ runner.os }}" echo "Runner arch: ${{ runner.arch }}" hostname - name: Install Lemonade run: | Invoke-WebRequest -Uri "https://github.com/lemonade-sdk/lemonade/releases/latest/download/lemonade.msi" -OutFile "lemonade.msi" msiexec /i lemonade.msi /qn /L*V install.log Start-Sleep -Seconds 5 - name: Refresh Environment and Test run: | $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") lemonade-server --help lemonade-sdk-lemonade-d88f5d9/.github/workflows/test_release_notes.yml000066400000000000000000000026141515430344400263110ustar00rootroot00000000000000name: Test Release Notes Formatting 📝 on: workflow_dispatch: permissions: contents: write jobs: test-release-notes: name: Preview Release Notes runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # Use the latest existing tag to test with real merged PR data - name: Get latest tag id: latest-tag run: | LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") if [ -n "$LATEST_TAG" ]; then # Extract version number (remove 'v' prefix) VERSION="${LATEST_TAG#v}" echo "Using existing tag $LATEST_TAG for testing" else # Fallback to CMakeLists version VERSION=$(grep -Po 'project\(lemon_cpp VERSION \K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt) echo "No tags found, using CMakeLists version $VERSION" fi echo "version=$VERSION" >> $GITHUB_OUTPUT - name: Generate release notes uses: ./.github/actions/generate-release-notes with: version: ${{ steps.latest-tag.outputs.version }} repo: ${{ github.repository }} github_token: ${{ secrets.GITHUB_TOKEN }} - name: Upload preview uses: actions/upload-artifact@v4 with: name: release-notes-preview path: release_notes.md retention-days: 7 lemonade-sdk-lemonade-d88f5d9/.gitignore000066400000000000000000000066471515430344400203040ustar00rootroot00000000000000### Python template # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST .DS_Store # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. # Note: Only ignore manifest files in build/dist directories, not source C++ manifests build/*.manifest dist/*.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py,cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. #Pipfile.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control #poetry.lock # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. #pdm.lock # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it # in version control. # https://pdm.fming.dev/latest/usage/project/#working-with-version-control .pdm.toml .pdm-python .pdm-build/ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ .aider* # Claude files CLAUDE.md CLAUDE.local.md .settings.local.json # Bruno/GitHub files .bruno/ # Cursor files (using .cursor/rules/*.mdc format) .cursor/ .cursor** # WiX Toolset build artifacts *.msi *.wixpdb lemonade-sdk-lemonade-d88f5d9/.pre-commit-config.yaml000066400000000000000000000005351515430344400225630ustar00rootroot00000000000000exclude: '\.svg$' repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - id: check-added-large-files - repo: https://github.com/psf/black rev: 26.1.0 hooks: - id: black language_version: python3 lemonade-sdk-lemonade-d88f5d9/.signpath/000077500000000000000000000000001515430344400201725ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.signpath/policies/000077500000000000000000000000001515430344400220015ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.signpath/policies/lemonade/000077500000000000000000000000001515430344400235655ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.signpath/policies/lemonade/test-signing.yml000066400000000000000000000003701515430344400267230ustar00rootroot00000000000000# SignPath policy for test-signing # See: https://docs.signpath.io/trusted-build-systems/github github-policies: runners: allowed_groups: - 'GitHub Actions' # GitHub-hosted runners - 'stx' # Ryzen AI self-hosted runners lemonade-sdk-lemonade-d88f5d9/.vscode/000077500000000000000000000000001515430344400176405ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/.vscode/c_cpp_properties.json000066400000000000000000000011271515430344400240740ustar00rootroot00000000000000{ "configurations": [ { "name": "Linux", "compileCommands": "${workspaceFolder}/build/compile_commands.json", "intelliSenseMode": "linux-clang-x64", "cStandard": "c17", "cppStandard": "c++17", "browse": { "path": [ "${workspaceFolder}/src/cpp/include", "${workspaceFolder}/build/include", "${workspaceFolder}/build" ], "limitSymbolsToIncludedHeaders": true } } ], "version": 4 } lemonade-sdk-lemonade-d88f5d9/.vscode/launch.json000066400000000000000000000012741515430344400220110ustar00rootroot00000000000000{ "version": "0.2.0", "configurations": [ { "name": "C++ Debug", "type": "cppdbg", "request": "launch", // The path to your compiled executable. Change this to match your build output. "program": "${workspaceFolder}/build/lemonade-router", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", // Configure GDB to work with the debugging session. "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ] } lemonade-sdk-lemonade-d88f5d9/.vscode/settings.json000066400000000000000000000065301515430344400223770ustar00rootroot00000000000000{ "cmake.sourceDirectory": "${workspaceFolder}", "files.watcherExclude": { "**/dist-app/**": true, "**/build/**": true, "**/app-src/**": true, "**/node_modules/**": true, "**/*.asar": true, "**/win-unpacked/**": true, "**/linux-unpacked/**": true, "**/mac/**": true }, "files.exclude": { "**/build/app-src/**": true, "**/build/app/**": true, "**/*.asar": true }, "search.exclude": { "**/dist-app/**": true, "**/build/**": true, "**/app-src/**": true, "**/node_modules/**": true }, "cursor.exclude": { "**/dist-app/**": true, "**/build/**": true, "**/app-src/**": true, "**/node_modules/**": true, "**/*.asar": true, "**/win-unpacked/**": true, "**/linux-unpacked/**": true, "**/mac/**": true }, "C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json", "C_Cpp.intelliSenseEngine": "disabled", "C_Cpp.default.intelliSenseMode": "linux-clang-x64", "clangd.arguments": [ "--compile-commands-dir=build", "--all-scopes-completion", "--background-index", "--clang-tidy", "--header-insertion=iwyu", "--completion-style=detailed", ], "cmake.debugConfig": { "args": [ "--llamacpp", "cpu" ] }, "files.associations": { "*.cpp": "cpp", "any": "cpp", "array": "cpp", "atomic": "cpp", "bit": "cpp", "bitset": "cpp", "cctype": "cpp", "chrono": "cpp", "cinttypes": "cpp", "clocale": "cpp", "cmath": "cpp", "codecvt": "cpp", "compare": "cpp", "complex": "cpp", "concepts": "cpp", "condition_variable": "cpp", "csignal": "cpp", "cstdarg": "cpp", "cstddef": "cpp", "cstdint": "cpp", "cstdio": "cpp", "cstdlib": "cpp", "cstring": "cpp", "ctime": "cpp", "cwchar": "cpp", "cwctype": "cpp", "deque": "cpp", "forward_list": "cpp", "list": "cpp", "map": "cpp", "set": "cpp", "string": "cpp", "unordered_map": "cpp", "unordered_set": "cpp", "vector": "cpp", "exception": "cpp", "algorithm": "cpp", "functional": "cpp", "iterator": "cpp", "memory": "cpp", "memory_resource": "cpp", "numeric": "cpp", "optional": "cpp", "random": "cpp", "ratio": "cpp", "regex": "cpp", "string_view": "cpp", "system_error": "cpp", "tuple": "cpp", "type_traits": "cpp", "utility": "cpp", "fstream": "cpp", "future": "cpp", "initializer_list": "cpp", "iomanip": "cpp", "iosfwd": "cpp", "iostream": "cpp", "istream": "cpp", "limits": "cpp", "mutex": "cpp", "new": "cpp", "numbers": "cpp", "ostream": "cpp", "ranges": "cpp", "semaphore": "cpp", "span": "cpp", "sstream": "cpp", "stdexcept": "cpp", "stop_token": "cpp", "streambuf": "cpp", "thread": "cpp", "typeinfo": "cpp", "valarray": "cpp", "variant": "cpp" }, } lemonade-sdk-lemonade-d88f5d9/CLAUDE.md000066400000000000000000000177661515430344400175770ustar00rootroot00000000000000# CLAUDE.md This file provides guidance to Claude Code and Claude-based code reviews when working with this repository. ## Project Overview Lemonade is a local LLM server (v9.4.x) providing GPU and NPU acceleration for running large language models on consumer hardware. It exposes OpenAI-compatible and Ollama-compatible REST APIs and supports multiple backends: llama.cpp, FastFlowLM, RyzenAI, whisper.cpp, stable-diffusion.cpp, and Kokoro TTS. ## Architecture ### Four Executables - **lemonade-router** — Pure HTTP server. Handles REST API, routes requests to backends, manages model loading/unloading. No CLI. - **lemonade-server** — CLI client. Commands: `list`, `pull`, `delete`, `run`, `serve`, `status`, `stop`, `logs`. Communicates with router via HTTP. - **lemonade-tray** — GUI launcher (Windows/macOS/Linux). Starts `lemonade-server serve` without a console. Platform code in `src/cpp/tray/platform/`. - **lemonade-log-viewer** — Windows-only log file viewer. ### Backend Abstraction `WrappedServer` (`src/cpp/include/lemon/wrapped_server.h`) is the abstract base class. Each backend inherits it and implements `install()`, `download_model()`, `load()`, `unload()`, and inference methods. Backends run as **subprocesses** — Lemonade forwards HTTP requests to them. | Backend | Class | Purpose | |---------|-------|---------| | llama.cpp | `LlamaCppServer` | LLM inference — CPU/GPU (Vulkan, ROCm, Metal) | | FastFlowLM | `FastFlowLMServer` | NPU inference (multi-modal: LLM, ASR, embeddings, reranking) | | RyzenAI | `RyzenAIServer` | Hybrid NPU inference | | whisper.cpp | `WhisperServer` | Audio transcription | | stable-diffusion.cpp | `SdServer` | Image generation, editing, variations | | Kokoro | `KokoroServer` | Text-to-speech | Capability interfaces: `ICompletionServer`, `IEmbeddingsServer`, `IRerankingServer`, `IAudioServer`, `IImageServer`, `ITextToSpeechServer` (defined in `server_capabilities.h`). ### Router & Multi-Model Support `Router` (`src/cpp/server/router.cpp`) manages a vector of `WrappedServer` instances. Routes requests based on model recipe, maintains LRU caches per model type (LLM, embedding, reranking, audio), and enforces NPU exclusivity. Configurable via `--max-loaded-models`. ### Model Manager & Recipe System `ModelManager` (`src/cpp/server/model_manager.cpp`) loads the registry from `src/cpp/resources/server_models.json`. Each model has "recipes" defining which backend and config to use. Backend versions are pinned in `src/cpp/resources/backend_versions.json`. Models download from Hugging Face. ### API Routes All core endpoints are registered under **4 path prefixes**: - `/api/v0/` — Legacy - `/api/v1/` — Current - `/v0/` — Legacy short - `/v1/` — OpenAI SDK / LiteLLM compatibility **Core endpoints:** `chat/completions`, `completions`, `embeddings`, `reranking`, `models`, `models/{id}`, `health`, `pull`, `load`, `unload`, `delete`, `params`, `install`, `uninstall`, `audio/transcriptions`, `audio/speech`, `images/generations`, `images/edits`, `images/variations`, `responses`, `stats`, `system-info`, `system-stats`, `log-level`, `logs/stream` **Ollama-compatible endpoints** (under `/api/` without version prefix): `chat`, `generate`, `tags`, `show`, `delete`, `pull`, `embed`, `embeddings`, `ps`, `version` Optional API key auth via `LEMONADE_API_KEY` env var. CORS enabled on all routes. ### Desktop & Web App - **Electron app** — React 19 + TypeScript in `src/app/`. Pure CSS (dark theme), context-based state. Key components: `ChatWindow.tsx`, `ModelManager.tsx`, `DownloadManager.tsx`, `BackendManager.tsx`. Feature panels: LLMChat, ImageGeneration, Transcription, TTS, Embedding, Reranking. - **Web app** — Browser-only version in `src/web-app/`. Symlinks source from `src/app/src/`. Built via CMake `BUILD_WEB_APP=ON`. Served at `/app`. ### Key Dependencies **C++ (FetchContent):** cpp-httplib, nlohmann/json, CLI11, libcurl, zstd, IXWebSocket (Windows/Linux), brotli (macOS). Platform SSL: Schannel (Windows), SecureTransport (macOS), OpenSSL (Linux). **Electron:** React 19, TypeScript 5.3, Webpack 5, Electron 39, markdown-it, highlight.js, katex. ## Build Commands ```bash # C++ server cd src/cpp && mkdir build && cd build cmake .. cmake --build . --config Release -j # Electron app cd src/app && npm install npm run build:win # or build:mac / build:linux # Windows MSI installer cd src/cpp/build && cmake --build . --config Release --target wix_installer_minimal # Linux .deb cd src/cpp/build && cpack ``` CMake presets: `default` (Ninja), `windows` (VS 2022), `debug` (Ninja Debug). ## Testing Integration tests in Python against a live server: ```bash pip install -r test/requirements.txt ./src/cpp/build/Release/lemonade-router.exe --port 8000 --log-level debug # Separate terminal python test/server_endpoints.py python test/server_llm.py python test/server_sd.py python test/server_whisper.py python test/server_tts.py python test/server_system_info.py python test/server_cli.py python test/test_ollama.py ``` Test utilities in `test/utils/` with `server_base.py` as the base class. ## Code Style ### C++ - C++17, `lemon::` namespace - `snake_case` for functions/variables, `CamelCase` for classes/types - 4-space indent, `#pragma once` for headers - Platform guards: `#ifdef _WIN32`, `#ifdef __APPLE__`, `#ifdef __linux__` ### Python - **Black** formatting (v26.1.0, enforced in CI) - Pylint with `.pylintrc` - Pre-commit hooks: trailing-whitespace, end-of-file-fixer, check-yaml, check-added-large-files ### TypeScript/React - React 19, pure CSS (dark theme), context-based state - UI/frontend changes are handled by core maintainers only ## Key Files | File | Purpose | |------|---------| | `CMakeLists.txt` | Root build config (version, deps, targets) | | `src/cpp/server/server.cpp` | HTTP route registration and all handlers | | `src/cpp/server/router.cpp` | Request routing and multi-model orchestration | | `src/cpp/server/model_manager.cpp` | Model registry, downloads, recipe resolution | | `src/cpp/include/lemon/wrapped_server.h` | Backend abstract base class | | `src/cpp/include/lemon/server_capabilities.h` | Backend capability interfaces | | `src/cpp/resources/server_models.json` | Model registry | | `src/cpp/resources/backend_versions.json` | Backend version pins | | `src/cpp/server/anthropic_api.cpp` | Anthropic API compatibility | | `src/cpp/server/ollama_api.cpp` | Ollama API compatibility | | `src/cpp/tray/tray_app.cpp` | Tray application UI and logic | | `src/app/src/renderer/ModelManager.tsx` | Model management UI | | `src/app/src/renderer/ChatWindow.tsx` | Chat interface | ## Critical Invariants These MUST be maintained in all changes: 1. **Quad-prefix registration** — Every new endpoint MUST be registered under `/api/v0/`, `/api/v1/`, `/v0/`, AND `/v1/`. 2. **NPU exclusivity** — Only one NPU backend can be loaded at a time. Router must unload existing NPU models before loading a new one. 3. **WrappedServer contract** — New backends MUST implement all virtual methods: `install()`, `download_model()`, `load()`, `unload()`. 4. **Subprocess model** — Backends run as subprocesses (llama-server, whisper-server, sd-server, koko). They must NOT run in-process. 5. **Recipe integrity** — Changes to `server_models.json` must have valid recipes referencing backends in `backend_versions.json`. 6. **Cross-platform** — Code must compile on Windows (MSVC), Linux (GCC/Clang), macOS (AppleClang). Platform-specific code must use `#ifdef` guards. 7. **No hardcoded paths** — Use path utilities. Windows/Linux/macOS paths differ. 8. **Thread safety** — Router serves concurrent HTTP requests. Shared state must be properly guarded. 9. **Ollama compatibility** — Changes to model listing or management must not break `/api/*` Ollama endpoints. 10. **API key passthrough** — When `LEMONADE_API_KEY` is set, all API routes must enforce authentication. ## Contributing - Open an Issue before submitting major PRs - UI/frontend changes are handled by core maintainers only - Python formatting with Black is required - PRs trigger CI for linting, formatting, and integration tests lemonade-sdk-lemonade-d88f5d9/CMakeLists.txt000066400000000000000000001660501515430344400210470ustar00rootroot00000000000000cmake_minimum_required(VERSION 3.10...3.28) # Use static runtime library on Windows to avoid DLL dependencies if(MSVC) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() project(lemon_cpp VERSION 10.0.0) # Export compile_commands.json for clangd/IntelliSense set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # Enable Objective-C++ for macOS Metal support if(APPLE) enable_language(OBJCXX) endif() # Prevent finding Homebrew libraries on macOS to use system ones if(APPLE) set(CMAKE_IGNORE_PATH "/opt/homebrew/lib" "/opt/homebrew/include") endif() # Set default install prefix to /opt on Linux if not specified if(UNIX AND NOT APPLE AND CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) set(CMAKE_INSTALL_PREFIX "/opt" CACHE PATH "Install prefix" FORCE) endif() # Include GNUInstallDirs for standard installation directories include(GNUInstallDirs) # ============================================================ # Electron source paths (used by both runtime and installers) # ============================================================ get_filename_component(ELECTRON_APP_SOURCE_DIR "${CMAKE_SOURCE_DIR}/src/app" ABSOLUTE) set(ELECTRON_APP_BUILD_DIR "${CMAKE_BINARY_DIR}/app") if(WIN32) set(ELECTRON_APP_UNPACKED_DIR "${ELECTRON_APP_BUILD_DIR}/win-unpacked") set(ELECTRON_EXE_NAME "Lemonade.exe") elseif(APPLE) set(ELECTRON_APP_UNPACKED_DIR "${ELECTRON_APP_BUILD_DIR}/mac-arm64") set(ELECTRON_EXE_NAME "Lemonade.app") else() set(ELECTRON_APP_UNPACKED_DIR "${ELECTRON_APP_BUILD_DIR}/linux-unpacked") # Electron builder uses lowercase product name on Linux set(ELECTRON_EXE_NAME "lemonade") endif() # ============================================================ # Web App source paths # ============================================================ get_filename_component(WEB_APP_SOURCE_DIR "${CMAKE_SOURCE_DIR}/src/web-app" ABSOLUTE) set(WEB_APP_BUILD_DIR "${CMAKE_BINARY_DIR}/resources/web-app") # C++ Standard set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # Try system packages first, fall back to FetchContent if not available # Define minimum version requirements based on FetchContent git tags set(MIN_NLOHMANN_JSON_VERSION "3.11.3") set(MIN_CLI11_VERSION "2.4.2") set(MIN_CURL_VERSION "8.5.0") set(MIN_ZSTD_VERSION "1.5.7") set(MIN_HTTPLIB_VERSION "0.26.0") find_package(PkgConfig QUIET) # Try to find system packages with version requirements find_package(nlohmann_json ${MIN_NLOHMANN_JSON_VERSION} QUIET) if(PkgConfig_FOUND) pkg_check_modules(CURL QUIET libcurl>=${MIN_CURL_VERSION}) pkg_check_modules(ZSTD QUIET libzstd>=${MIN_ZSTD_VERSION}) pkg_check_modules(HTTPLIB QUIET cpp-httplib>=${MIN_HTTPLIB_VERSION}) endif() find_path(CLI11_INCLUDE_DIRS "CLI/CLI.hpp") find_path(HTTPLIB_INCLUDE_DIRS "httplib.h") # Verify CLI11 version if found if(CLI11_INCLUDE_DIRS) if(EXISTS "${CLI11_INCLUDE_DIRS}/CLI/Version.hpp") file(STRINGS "${CLI11_INCLUDE_DIRS}/CLI/Version.hpp" CLI11_VERSION_LINE REGEX "^#define CLI11_VERSION ") if(CLI11_VERSION_LINE) string(REGEX REPLACE "^#define CLI11_VERSION \"([0-9]+\\.[0-9]+\\.[0-9]+)\"" "\\1" CLI11_VERSION "${CLI11_VERSION_LINE}") if(CLI11_VERSION VERSION_LESS MIN_CLI11_VERSION) message(STATUS "System CLI11 version ${CLI11_VERSION} is less than required ${MIN_CLI11_VERSION}") unset(CLI11_INCLUDE_DIRS) endif() endif() endif() endif() # Determine which dependencies need to be fetched set(USE_SYSTEM_JSON ${nlohmann_json_FOUND}) set(USE_SYSTEM_CURL ${CURL_FOUND}) set(USE_SYSTEM_ZSTD ${ZSTD_FOUND}) set(USE_SYSTEM_CLI11 ${CLI11_INCLUDE_DIRS}) set(USE_SYSTEM_HTTPLIB ${HTTPLIB_FOUND}) # On macOS, always statically link dependencies to avoid Homebrew dylib issues if(APPLE) set(USE_SYSTEM_ZSTD OFF) set(USE_SYSTEM_CURL OFF) set(USE_SYSTEM_HTTPLIB OFF) endif() if(USE_SYSTEM_JSON) message(STATUS "Using system nlohmann_json (>= ${MIN_NLOHMANN_JSON_VERSION})") else() message(STATUS "System nlohmann_json not found or version < ${MIN_NLOHMANN_JSON_VERSION}, will use FetchContent") endif() if(USE_SYSTEM_CURL) message(STATUS "Using system libcurl (version ${CURL_VERSION}, >= ${MIN_CURL_VERSION})") else() message(STATUS "System libcurl not found or version < ${MIN_CURL_VERSION}, will use FetchContent") endif() if(USE_SYSTEM_ZSTD) message(STATUS "Using system zstd (version ${ZSTD_VERSION}, >= ${MIN_ZSTD_VERSION})") else() message(STATUS "System zstd not found or version < ${MIN_ZSTD_VERSION}, will use FetchContent") endif() if(USE_SYSTEM_CLI11) if(CLI11_VERSION) message(STATUS "Using system CLI11 (version ${CLI11_VERSION}, >= ${MIN_CLI11_VERSION})") else() message(STATUS "Using system CLI11 (>= ${MIN_CLI11_VERSION})") endif() else() message(STATUS "System CLI11 not found or version < ${MIN_CLI11_VERSION}, will use FetchContent") endif() if(USE_SYSTEM_HTTPLIB) if(HTTPLIB_VERSION) message(STATUS "Using system cpp-httplib (version ${HTTPLIB_VERSION}, >= ${MIN_HTTPLIB_VERSION})") else() message(STATUS "Using system cpp-httplib (>= ${MIN_HTTPLIB_VERSION})") endif() else() message(STATUS "System cpp-httplib not found or version < ${MIN_HTTPLIB_VERSION}, will use FetchContent") endif() # ============================================================ # Common dependency configurations # ============================================================ # Common configurations for all dependencies set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "") # Build static libraries # Link to MSVC library statically set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") # Fetch missing dependencies if(NOT USE_SYSTEM_JSON OR NOT USE_SYSTEM_CURL OR NOT USE_SYSTEM_ZSTD OR NOT USE_SYSTEM_CLI11 OR NOT USE_SYSTEM_HTTPLIB) include(FetchContent) endif() if(APPLE) # brotli (MIT License) - required by httplib for compression on macOS FetchContent_Declare(brotli GIT_REPOSITORY https://github.com/google/brotli.git GIT_TAG v1.1.0 EXCLUDE_FROM_ALL ) endif() # === nlohmann/json === if(NOT USE_SYSTEM_JSON) FetchContent_Declare(json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG v${MIN_NLOHMANN_JSON_VERSION} ) set(JSON_BuildTests OFF CACHE INTERNAL "") set(JSON_Install OFF CACHE INTERNAL "") FetchContent_MakeAvailable(json) endif() # === CLI11 === if(NOT USE_SYSTEM_CLI11) FetchContent_Declare(CLI11 GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git GIT_TAG v${MIN_CLI11_VERSION} ) set(CLI11_BUILD_TESTS OFF CACHE INTERNAL "") set(CLI11_BUILD_EXAMPLES OFF CACHE INTERNAL "") FetchContent_MakeAvailable(CLI11) endif() # === curl === if(NOT USE_SYSTEM_CURL) string(REPLACE "." "_" CURL_TAG_VERSION "${MIN_CURL_VERSION}") FetchContent_Declare(curl GIT_REPOSITORY https://github.com/curl/curl.git GIT_TAG curl-${CURL_TAG_VERSION} EXCLUDE_FROM_ALL ) set(BUILD_STATIC_LIBS ON CACHE INTERNAL "") set(BUILD_CURL_EXE OFF CACHE INTERNAL "") set(CURL_DISABLE_TESTS ON CACHE INTERNAL "") set(CURL_DISABLE_INSTALL ON CACHE INTERNAL "") set(HTTP_ONLY ON CACHE INTERNAL "") if(WIN32) set(CURL_STATIC_CRT ON CACHE INTERNAL "") set(CURL_USE_SCHANNEL ON CACHE INTERNAL "") set(CMAKE_USE_SCHANNEL ON CACHE INTERNAL "") elseif(APPLE) set(CURL_USE_SECTRANSP ON CACHE INTERNAL "") else() set(CURL_USE_OPENSSL ON CACHE INTERNAL "") endif() FetchContent_MakeAvailable(curl) endif() # === zstd === if(NOT USE_SYSTEM_ZSTD) FetchContent_Declare(zstd GIT_REPOSITORY https://github.com/facebook/zstd.git GIT_TAG v${MIN_ZSTD_VERSION} SOURCE_SUBDIR build/cmake EXCLUDE_FROM_ALL ) set(ZSTD_BUILD_STATIC ON CACHE INTERNAL "") set(ZSTD_BUILD_PROGRAMS OFF CACHE INTERNAL "") set(ZSTD_BUILD_SHARED OFF CACHE INTERNAL "") set(ZSTD_BUILD_TESTS OFF CACHE INTERNAL "") FetchContent_MakeAvailable(zstd) add_library(zstd::libzstd ALIAS libzstd_static) else() # Create an imported target for system zstd so httplib can find it if(NOT TARGET zstd::libzstd) add_library(zstd::libzstd UNKNOWN IMPORTED) # Find the actual library file find_library(ZSTD_LIBRARY NAMES zstd HINTS ${ZSTD_LIBRARY_DIRS}) if(ZSTD_LIBRARY) set_target_properties(zstd::libzstd PROPERTIES IMPORTED_LOCATION "${ZSTD_LIBRARY}" IMPORTED_LOCATION_RELEASE "${ZSTD_LIBRARY}" IMPORTED_LOCATION_DEBUG "${ZSTD_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${ZSTD_INCLUDE_DIRS}" ) endif() endif() endif() if(APPLE) # === brotli === set(BROTLI_BUILD_STATIC ON CACHE INTERNAL "") set(BROTLI_BUILD_PROGRAMS OFF CACHE INTERNAL "") set(BROTLI_BUILD_SHARED OFF CACHE INTERNAL "") set(BROTLI_BUILD_TESTS OFF CACHE INTERNAL "") FetchContent_MakeAvailable(brotli) if(TARGET brotlicommon) add_library(Brotli::common ALIAS brotlicommon) endif() if(TARGET brotlienc) add_library(Brotli::encoder ALIAS brotlienc) endif() if(TARGET brotlidec) add_library(Brotli::decoder ALIAS brotlidec) endif() endif() # === httplib === if(NOT USE_SYSTEM_HTTPLIB) FetchContent_Declare(httplib GIT_REPOSITORY https://github.com/yhirose/cpp-httplib.git GIT_TAG v${MIN_HTTPLIB_VERSION} ) set(HTTPLIB_REQUIRE_OPENSSL OFF CACHE INTERNAL "") set(HTTPLIB_USE_OPENSSL_IF_AVAILABLE OFF CACHE INTERNAL "") set(HTTPLIB_REQUIRE_ZSTD OFF CACHE INTERNAL "") set(HTTPLIB_USE_ZSTD_IF_AVAILABLE OFF CACHE INTERNAL "") set(HTTPLIB_IS_USING_ZSTD ON CACHE INTERNAL "") set(HTTPLIB_INSTALL OFF CACHE INTERNAL "") FetchContent_MakeAvailable(httplib) if(NOT USE_SYSTEM_ZSTD) target_include_directories(httplib INTERFACE ${zstd_SOURCE_DIR}/lib ${zstd_SOURCE_DIR}/lib/common ) endif() endif() # === IXWebSocket (for WebSocket support: Windows and Linux) === if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") set(MIN_IXWEBSOCKET_VERSION "11.4.4") FetchContent_Declare(ixwebsocket GIT_REPOSITORY https://github.com/machinezone/IXWebSocket.git GIT_TAG v${MIN_IXWEBSOCKET_VERSION} ) # Disable TLS and ZLIB for simpler setup (localhost only, no compression needed) set(USE_TLS OFF CACHE INTERNAL "") set(USE_OPEN_SSL OFF CACHE INTERNAL "") set(USE_MBED_TLS OFF CACHE INTERNAL "") set(USE_ZLIB OFF CACHE INTERNAL "") set(IXWEBSOCKET_INSTALL OFF CACHE INTERNAL "") FetchContent_MakeAvailable(ixwebsocket) message(STATUS "IXWebSocket will be fetched (v${MIN_IXWEBSOCKET_VERSION})") endif() # Use brotli for compression on macOS if(APPLE) set(HTTPLIB_REQUIRE_BROTLI OFF CACHE INTERNAL "") # Skip brotli checks set(HTTPLIB_USE_BROTLI_IF_AVAILABLE OFF CACHE INTERNAL "") # Skip brotli checks set(HTTPLIB_IS_USING_BROTLI ON CACHE INTERNAL "") # brotli is provided endif() # ============================================================ # Application: lemonade-router # ============================================================ set(EXECUTABLE_NAME "lemonade-router") # ============================================================ # Compilation configurations # ============================================================ # Common compiler definitions # Enable httplib thread pool with 8 threads add_compile_definitions(CPPHTTPLIB_THREAD_POOL_COUNT=8) # Platform-specific compiler definitions if(WIN32) # Set Windows target version to Windows 10 for httplib v0.26.0 add_compile_definitions(_WIN32_WINNT=0x0A00) if(MSVC) # Add security-hardening compiler flags for MSVC # Control Flow Guard - prevents control flow hijacking add_compile_options(/guard:cf) # Buffer Security Check - stack buffer overflow detection add_compile_options(/GS) endif() endif() # ============================================================ # Compilation # ============================================================ # Generate version header from template configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/include/lemon/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/include/lemon/version.h @ONLY ) # Generate manifest files from templates (Windows only) if(WIN32) configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/server/lemonade.manifest.in ${CMAKE_CURRENT_BINARY_DIR}/server/lemonade.manifest @ONLY ) # ============================================================ # WiX Toolset Configuration (Windows MSI Installer) # ============================================================ # Configure WiX Product.wxs template with version configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/installer/Product.wxs.in ${CMAKE_CURRENT_BINARY_DIR}/installer/Product.wxs @ONLY ) set(WIX_PRODUCT_WXS "${CMAKE_CURRENT_BINARY_DIR}/installer/Product.wxs") set(WIX_FRAGMENT_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/installer/generate_electron_fragment.py") # Find WiX Toolset 5.0+ (unified 'wix' command) and Python for fragment generation find_program(WIX_EXECUTABLE wix) find_package(Python3 COMPONENTS Interpreter) if(WIX_EXECUTABLE) execute_process( COMMAND ${WIX_EXECUTABLE} --version OUTPUT_VARIABLE WIX_VERSION_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE ) message(STATUS "WiX Toolset found: ${WIX_EXECUTABLE}") message(STATUS "WiX version: ${WIX_VERSION_OUTPUT}") file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}" WIX_SOURCE_DIR_NATIVE) file(TO_NATIVE_PATH "${WIX_PRODUCT_WXS}" WIX_PRODUCT_WXS_NATIVE) file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lemonade-server-minimal.msi" WIX_MINIMAL_OUTPUT_NATIVE) file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lemonade.msi" WIX_FULL_OUTPUT_NATIVE) file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/cpp" WIX_CPP_SOURCE_DIR_NATIVE) file(TO_NATIVE_PATH "${CMAKE_BINARY_DIR}" WIX_BUILD_DIR_NATIVE) file(TO_NATIVE_PATH "${ELECTRON_APP_UNPACKED_DIR}" WIX_ELECTRON_SOURCE_NATIVE) set(WIX_ELECTRON_FRAGMENT "${CMAKE_CURRENT_BINARY_DIR}/installer/ElectronAppFragment.wxs") file(TO_NATIVE_PATH "${WIX_ELECTRON_FRAGMENT}" WIX_ELECTRON_FRAGMENT_NATIVE) # Web app fragment paths file(TO_NATIVE_PATH "${WEB_APP_BUILD_DIR}" WIX_WEBAPP_SOURCE_NATIVE) set(WIX_WEBAPP_FRAGMENT "${CMAKE_CURRENT_BINARY_DIR}/installer/WebAppFragment.wxs") file(TO_NATIVE_PATH "${WIX_WEBAPP_FRAGMENT}" WIX_WEBAPP_FRAGMENT_NATIVE) # Minimal installer (server only, optionally with web-app if built) if(Python3_FOUND) add_custom_target(wix_installer_minimal COMMAND ${CMAKE_COMMAND} -E echo "Building WiX MSI installer (server only)..." # Generate web-app fragment if web-app directory exists COMMAND ${Python3_EXECUTABLE} ${WIX_FRAGMENT_SCRIPT} --source "${WEB_APP_BUILD_DIR}" --output "${WIX_WEBAPP_FRAGMENT}" --component-group "WebAppComponents" --root-id "WebAppDir" --path-variable "WebAppSourceDir" || ${CMAKE_COMMAND} -E echo "Web-app fragment generation skipped (web-app not built)" COMMAND ${WIX_EXECUTABLE} build -arch x64 -ext WixToolset.UI.wixext -d SourceDir="${WIX_SOURCE_DIR_NATIVE}" -d CppSourceDir="${WIX_CPP_SOURCE_DIR_NATIVE}" -d BuildDir="${WIX_BUILD_DIR_NATIVE}" -d IncludeElectron=0 -d IncludeWebApp=1 -d WebAppSourceDir="${WIX_WEBAPP_SOURCE_NATIVE}" -out "${WIX_MINIMAL_OUTPUT_NATIVE}" "${WIX_PRODUCT_WXS_NATIVE}" "${WIX_WEBAPP_FRAGMENT_NATIVE}" || ${WIX_EXECUTABLE} build -arch x64 -ext WixToolset.UI.wixext -d SourceDir="${WIX_SOURCE_DIR_NATIVE}" -d CppSourceDir="${WIX_CPP_SOURCE_DIR_NATIVE}" -d BuildDir="${WIX_BUILD_DIR_NATIVE}" -d IncludeElectron=0 -d IncludeWebApp=0 -out "${WIX_MINIMAL_OUTPUT_NATIVE}" "${WIX_PRODUCT_WXS_NATIVE}" COMMAND ${CMAKE_COMMAND} -E echo "MSI installer created: lemonade-server-minimal.msi" DEPENDS ${EXECUTABLE_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Building WiX MSI installer (server only)" ) else() add_custom_target(wix_installer_minimal COMMAND ${CMAKE_COMMAND} -E echo "Building WiX MSI installer (server only, no web-app)..." COMMAND ${WIX_EXECUTABLE} build -arch x64 -ext WixToolset.UI.wixext -d SourceDir="${WIX_SOURCE_DIR_NATIVE}" -d CppSourceDir="${WIX_CPP_SOURCE_DIR_NATIVE}" -d BuildDir="${WIX_BUILD_DIR_NATIVE}" -d IncludeElectron=0 -d IncludeWebApp=0 -out "${WIX_MINIMAL_OUTPUT_NATIVE}" "${WIX_PRODUCT_WXS_NATIVE}" COMMAND ${CMAKE_COMMAND} -E echo "MSI installer created: lemonade-server-minimal.msi" DEPENDS ${EXECUTABLE_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Building WiX MSI installer (server only)" ) endif() set(WIX_INSTALLER_TARGETS wix_installer_minimal) # Add web-app dependency to minimal installer if target exists if(TARGET web-app) add_dependencies(wix_installer_minimal web-app) endif() if(Python3_FOUND) add_custom_target(wix_installer_full COMMAND ${CMAKE_COMMAND} -E echo "Building WiX MSI installer (with Electron app)..." # Generate web-app fragment COMMAND ${Python3_EXECUTABLE} ${WIX_FRAGMENT_SCRIPT} --source "${WEB_APP_BUILD_DIR}" --output "${WIX_WEBAPP_FRAGMENT}" --component-group "WebAppComponents" --root-id "WebAppDir" --path-variable "WebAppSourceDir" || ${CMAKE_COMMAND} -E echo "Warning: Web-app fragment generation failed" # Generate Electron app fragment COMMAND ${Python3_EXECUTABLE} ${WIX_FRAGMENT_SCRIPT} --source "${ELECTRON_APP_UNPACKED_DIR}" --output "${WIX_ELECTRON_FRAGMENT}" --component-group "ElectronAppComponents" --root-id "ElectronAppDir" --path-variable "ElectronSourceDir" COMMAND ${WIX_EXECUTABLE} build -arch x64 -ext WixToolset.UI.wixext -d SourceDir="${WIX_SOURCE_DIR_NATIVE}" -d CppSourceDir="${WIX_CPP_SOURCE_DIR_NATIVE}" -d BuildDir="${WIX_BUILD_DIR_NATIVE}" -d IncludeElectron=1 -d IncludeWebApp=1 -d ElectronSourceDir="${WIX_ELECTRON_SOURCE_NATIVE}" -d WebAppSourceDir="${WIX_WEBAPP_SOURCE_NATIVE}" -out "${WIX_FULL_OUTPUT_NATIVE}" "${WIX_PRODUCT_WXS_NATIVE}" "${WIX_ELECTRON_FRAGMENT_NATIVE}" "${WIX_WEBAPP_FRAGMENT_NATIVE}" || ${WIX_EXECUTABLE} build -arch x64 -ext WixToolset.UI.wixext -d SourceDir="${WIX_SOURCE_DIR_NATIVE}" -d CppSourceDir="${WIX_CPP_SOURCE_DIR_NATIVE}" -d BuildDir="${WIX_BUILD_DIR_NATIVE}" -d IncludeElectron=1 -d IncludeWebApp=0 -d ElectronSourceDir="${WIX_ELECTRON_SOURCE_NATIVE}" -out "${WIX_FULL_OUTPUT_NATIVE}" "${WIX_PRODUCT_WXS_NATIVE}" "${WIX_ELECTRON_FRAGMENT_NATIVE}" COMMAND ${CMAKE_COMMAND} -E echo "MSI installer created: lemonade.msi" DEPENDS ${EXECUTABLE_NAME} WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMENT "Building WiX MSI installer (with Electron app)" ) if(TARGET electron-app) add_dependencies(wix_installer_full electron-app) endif() if(TARGET web-app) add_dependencies(wix_installer_full web-app) endif() list(APPEND WIX_INSTALLER_TARGETS wix_installer_full) else() message(STATUS "Python 3 interpreter not found. Skipping the Electron-enabled MSI target 'wix_installer_full'.") endif() add_custom_target(wix_installers DEPENDS ${WIX_INSTALLER_TARGETS} ) message(STATUS "WiX installer targets configured. Run 'cmake --build . --target wix_installer_minimal' or 'wix_installer_full'.") else() message(STATUS "WiX Toolset not found. MSI installers will not be available.") message(STATUS " Install WiX Toolset 5.0.2 from: https://github.com/wixtoolset/wix/releases/download/v5.0.2/wix-cli-x64.msi") message(STATUS " Or visit: https://wixtoolset.org/") endif() endif() # Include directories include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/include ${CMAKE_CURRENT_BINARY_DIR}/include ${CMAKE_CURRENT_BINARY_DIR} ) # Source files set(SOURCES src/cpp/server/main.cpp src/cpp/server/server.cpp src/cpp/server/router.cpp src/cpp/server/cli_parser.cpp src/cpp/server/model_manager.cpp src/cpp/server/wrapped_server.cpp src/cpp/server/streaming_proxy.cpp src/cpp/server/system_info.cpp src/cpp/server/recipe_options.cpp src/cpp/server/utils/http_client.cpp src/cpp/server/utils/json_utils.cpp src/cpp/server/utils/process_manager.cpp src/cpp/server/utils/path_utils.cpp src/cpp/server/utils/version_utils.cpp src/cpp/server/utils/wmi_helper.cpp src/cpp/server/utils/network_beacon.cpp src/cpp/server/backends/llamacpp_server.cpp src/cpp/server/backends/fastflowlm_server.cpp src/cpp/server/backends/ryzenaiserver.cpp src/cpp/server/backends/whisper_server.cpp src/cpp/server/backends/kokoro_server.cpp src/cpp/server/backends/sd_server.cpp src/cpp/server/backends/backend_utils.cpp src/cpp/server/backend_manager.cpp src/cpp/server/ollama_api.cpp src/cpp/server/anthropic_api.cpp $<$,$>:src/cpp/server/streaming_audio_buffer.cpp> $<$,$>:src/cpp/server/vad.cpp> $<$,$>:src/cpp/server/realtime_session.cpp> $<$,$>:src/cpp/server/websocket_server.cpp> ) # Add platform-specific source files if(APPLE) list(APPEND SOURCES src/cpp/server/macos_system_info.mm) set_source_files_properties(src/cpp/server/macos_system_info.mm PROPERTIES LANGUAGE OBJCXX) endif() # Add version resource file on Windows if(WIN32) list(APPEND SOURCES src/cpp/server/version.rc) endif() # Create executable add_executable(${EXECUTABLE_NAME} ${SOURCES}) # ============================================================ # Linking configurations # ============================================================ # Platform-specific linking options if(WIN32) if(MSVC) # Linker security flags add_link_options( /DYNAMICBASE # Address Space Layout Randomization (ASLR) /NXCOMPAT # Data Execution Prevention (DEP) /GUARD:CF # Control Flow Guard ) # Embed manifest file set_target_properties(${EXECUTABLE_NAME} PROPERTIES LINK_FLAGS "/MANIFEST:EMBED /MANIFESTINPUT:${CMAKE_CURRENT_BINARY_DIR}/server/lemonade.manifest" ) endif() endif() # ============================================================ # Linking # ============================================================ # Common linking if(USE_SYSTEM_JSON) target_link_libraries(${EXECUTABLE_NAME} PRIVATE nlohmann_json::nlohmann_json) else() target_link_libraries(${EXECUTABLE_NAME} PRIVATE nlohmann_json::nlohmann_json) endif() if(USE_SYSTEM_HTTPLIB) target_link_libraries(${EXECUTABLE_NAME} PRIVATE cpp-httplib) else() target_link_libraries(${EXECUTABLE_NAME} PRIVATE httplib::httplib) endif() if(USE_SYSTEM_CLI11) target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CLI11_INCLUDE_DIRS}) else() target_link_libraries(${EXECUTABLE_NAME} PRIVATE CLI11::CLI11) endif() if(USE_SYSTEM_CURL) target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${CURL_LIBRARIES}) target_include_directories(${EXECUTABLE_NAME} PRIVATE ${CURL_INCLUDE_DIRS}) target_compile_options(${EXECUTABLE_NAME} PRIVATE ${CURL_CFLAGS_OTHER}) else() target_link_libraries(${EXECUTABLE_NAME} PRIVATE libcurl) endif() if(USE_SYSTEM_ZSTD) target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${ZSTD_LIBRARIES}) target_include_directories(${EXECUTABLE_NAME} PRIVATE ${ZSTD_INCLUDE_DIRS}) target_link_directories(${EXECUTABLE_NAME} PRIVATE ${ZSTD_LIBRARY_DIRS}) target_compile_options(${EXECUTABLE_NAME} PRIVATE ${ZSTD_CFLAGS_OTHER}) endif() if(USE_SYSTEM_HTTPLIB AND HTTPLIB_INCLUDE_DIRS) target_include_directories(${EXECUTABLE_NAME} PRIVATE ${HTTPLIB_INCLUDE_DIRS}) endif() # IXWebSocket for WebSocket support (Windows and Linux) if(WIN32 OR CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(${EXECUTABLE_NAME} PRIVATE ixwebsocket) target_compile_definitions(${EXECUTABLE_NAME} PRIVATE LEMON_HAS_WEBSOCKET) endif() # Enable ARC (Automatic Reference Counting) for macOS Objective-C++ files if(APPLE) target_compile_options(${EXECUTABLE_NAME} PRIVATE -fobjc-arc) endif() # Platform-specific linking if(WIN32) target_link_libraries(${EXECUTABLE_NAME} PRIVATE ws2_32 wsock32 wbemuuid ole32 oleaut32 ) elseif(APPLE) target_link_libraries(${EXECUTABLE_NAME} PRIVATE "-framework Metal" "-framework Foundation" "-framework CoreServices" "-lobjc" ) # Prevent linking to Homebrew OpenSSL on macOS, use Apple's built-in SSL target_link_options(${EXECUTABLE_NAME} PRIVATE -Wl,-no_weak_imports) elseif(UNIX) # Link systemd for journal support if(PkgConfig_FOUND) pkg_check_modules(SYSTEMD QUIET libsystemd) if(SYSTEMD_FOUND) set(LEMONADE_SYSTEMD_UNIT_NAME "lemonade-server.service" CACHE STRING "Systemd unit name to match for journald log mode (overridable at runtime via LEMONADE_SYSTEMD_UNIT env var)") target_include_directories(${EXECUTABLE_NAME} PRIVATE ${SYSTEMD_INCLUDE_DIRS}) target_link_libraries(${EXECUTABLE_NAME} PRIVATE ${SYSTEMD_LIBRARIES}) target_compile_definitions(${EXECUTABLE_NAME} PRIVATE HAVE_SYSTEMD LEMONADE_SYSTEMD_UNIT_NAME="${LEMONADE_SYSTEMD_UNIT_NAME}") endif() endif() endif() # ============================================================ # Resources # ============================================================ # Resources are self-contained in src/cpp/resources/: # - static/index.html, static/favicon.ico (web UI) # - server_models.json (model registry) # - backend_versions.json (backend version configuration) # Collect resource files for dependency tracking file(GLOB_RECURSE RESOURCE_FILES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/resources/*" ) # Create a custom target that depends on resource files # This ensures resources are copied when source files change add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/resources.stamp COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/resources ${CMAKE_BINARY_DIR}/resources COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/resources.stamp DEPENDS ${RESOURCE_FILES} COMMENT "Copying resources to build directory" ) add_custom_target(copy_resources ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/resources.stamp ) # Copy resources to the runtime directory after build add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_BINARY_DIR}/resources $/resources COMMENT "Copying resources to output directory" ) # Ensure resources are copied before building the executable add_dependencies(${EXECUTABLE_NAME} copy_resources) # ============================================================ # Electron App Integration (Cross-Platform) # ============================================================ # Find Node.js and npm for building the Electron app find_program(NODE_EXECUTABLE node) # On Windows, npm is a batch file (npm.cmd), so we need to find it explicitly # or invoke it through cmd.exe to avoid MSBuild custom build step issues if(WIN32) find_program(NPM_EXECUTABLE npm.cmd) else() find_program(NPM_EXECUTABLE npm) endif() # Custom target to build the Electron app (must be called manually) if(NODE_EXECUTABLE AND NPM_EXECUTABLE) if(WIN32) set(ELECTRON_BUILD_TARGET "build:win") # On Windows, wrap npm calls with cmd /c to handle batch file execution properly set(NPM_COMMAND cmd /c "${NPM_EXECUTABLE}") elseif(APPLE) set(ELECTRON_BUILD_TARGET "build:mac") set(NPM_COMMAND "${NPM_EXECUTABLE}") else() set(ELECTRON_BUILD_TARGET "build:linux") set(NPM_COMMAND "${NPM_EXECUTABLE}") endif() set(ELECTRON_BUILD_SOURCE_DIR "${CMAKE_BINARY_DIR}/app-src") # Note: On Windows, NPM_COMMAND uses 'cmd /c' wrapper to handle npm.cmd batch file # Note: Avoid shell-special characters in echo messages (no parentheses, etc.) add_custom_target(electron-app COMMAND ${CMAKE_COMMAND} -E echo "Copying Electron app source to build directory..." COMMAND ${CMAKE_COMMAND} -E remove_directory "${ELECTRON_BUILD_SOURCE_DIR}" COMMAND ${CMAKE_COMMAND} -E make_directory "${ELECTRON_BUILD_SOURCE_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_directory "${ELECTRON_APP_SOURCE_DIR}" "${ELECTRON_BUILD_SOURCE_DIR}" COMMAND ${CMAKE_COMMAND} -E echo "Installing npm dependencies..." COMMAND ${CMAKE_COMMAND} -E chdir "${ELECTRON_BUILD_SOURCE_DIR}" ${NPM_COMMAND} ci COMMAND ${CMAKE_COMMAND} -E echo "Building Electron app for current platform..." COMMAND ${CMAKE_COMMAND} -E chdir "${ELECTRON_BUILD_SOURCE_DIR}" ${NPM_COMMAND} run ${ELECTRON_BUILD_TARGET} -- --config.directories.output=${ELECTRON_APP_BUILD_DIR} COMMENT "Building Electron app with npm" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" ) message(STATUS "Node.js found: ${NODE_EXECUTABLE}") message(STATUS "npm found: ${NPM_EXECUTABLE}") if(WIN32) message(STATUS "Electron app can be built with: cmake --build --preset windows --target electron-app") else() message(STATUS "Electron app can be built with: cmake --build --preset default --target electron-app") endif() else() message(STATUS "Node.js or npm not found - Electron app build target disabled") message(STATUS "Install Node.js to enable: https://nodejs.org/") endif() # ============================================================ # Web App Integration (Cross-Platform) # ============================================================ # On Windows, disable automatic build by default (can be enabled with -DBUILD_WEB_APP=ON) # On other platforms, enable automatic build by default (can be disabled with -DBUILD_WEB_APP=OFF) if(WIN32) option(BUILD_WEB_APP "Build the Web app automatically" OFF) else() option(BUILD_WEB_APP "Build the Web app automatically" ON) endif() # Custom target to build the Web app with proper dependency tracking if(NODE_EXECUTABLE AND NPM_EXECUTABLE AND BUILD_WEB_APP) # Create a stamp file to track build state set(WEB_APP_STAMP "${CMAKE_CURRENT_BINARY_DIR}/web-app.stamp") set(WEB_APP_BUILD_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/web-app-src") # Get all source files in src/web-app (excluding node_modules and dist) file(GLOB_RECURSE WEB_APP_SOURCES CONFIGURE_DEPENDS "${WEB_APP_SOURCE_DIR}/src/*" "${WEB_APP_SOURCE_DIR}/*.json" "${WEB_APP_SOURCE_DIR}/*.js" "${WEB_APP_SOURCE_DIR}/*.ts" "${WEB_APP_SOURCE_DIR}/*.tsx" "${WEB_APP_SOURCE_DIR}/*.css" ) # Add custom command to build web app (only runs if sources change) add_custom_command( OUTPUT ${WEB_APP_STAMP} COMMAND ${CMAKE_COMMAND} -DWEB_APP_SOURCE_DIR=${WEB_APP_SOURCE_DIR} -DWEB_APP_BUILD_SOURCE_DIR=${WEB_APP_BUILD_SOURCE_DIR} -DWEB_APP_BUILD_DIR=${WEB_APP_BUILD_DIR} -DNPM_EXECUTABLE=${NPM_EXECUTABLE} -DWEB_APP_STAMP=${WEB_APP_STAMP} -P ${CMAKE_CURRENT_SOURCE_DIR}/src/web-app/BuildWebApp.cmake DEPENDS ${WEB_APP_SOURCES} COMMENT "Building Web app with npm" ) # Add custom target that depends on the stamp file add_custom_target(web-app ALL DEPENDS ${WEB_APP_STAMP}) message(STATUS "Web app will be built automatically (disable with -DBUILD_WEB_APP=OFF)") if (NOT WIN32 AND NOT APPLE AND NOT BUILD_ELECTRON_APP) # Install .desktop file for web app install(FILES ${CMAKE_SOURCE_DIR}/data/lemonade-web-app.desktop DESTINATION share/applications ) # Create symlink in standard applications path only if not installing to /usr if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") install(CODE " file(MAKE_DIRECTORY \"\$ENV{DESTDIR}/usr/share/applications\") execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/share/applications/lemonade-web-app.desktop \"\$ENV{DESTDIR}/usr/share/applications/lemonade-web-app.desktop\" ) ") endif() install(FILES ${CMAKE_SOURCE_DIR}/src/web-app/assets/logo.svg DESTINATION share/pixmaps RENAME lemonade-app.svg ) # Create symlink in standard pixmaps path only if not installing to /usr if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") install(CODE " file(MAKE_DIRECTORY \"\$ENV{DESTDIR}/usr/share/pixmaps\") execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/share/pixmaps/lemonade-app.svg \"\$ENV{DESTDIR}/usr/share/pixmaps/lemonade-app.svg\" ) ") endif() # Install launch script for web app install(FILES ${CMAKE_SOURCE_DIR}/data/lemonade-web-app DESTINATION bin PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) # Create symlink in standard bin path only if not installing to /usr if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") install(CODE " file(MAKE_DIRECTORY \"\$ENV{DESTDIR}/usr/bin\") execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/bin/lemonade-web-app \"\$ENV{DESTDIR}/usr/bin/lemonade-web-app\" ) ") endif() endif() else() if(NOT NODE_EXECUTABLE OR NOT NPM_EXECUTABLE) message(STATUS "Node.js or npm not found - Web app build target disabled") else() message(STATUS "Web app automatic build disabled (enable with -DBUILD_WEB_APP=ON)") endif() endif() # Add manual web-app target for platforms where automatic build is disabled if(NODE_EXECUTABLE AND NPM_EXECUTABLE AND NOT BUILD_WEB_APP) add_custom_target(web-app COMMAND ${CMAKE_COMMAND} -E echo "Building Web app..." COMMAND ${CMAKE_COMMAND} -E chdir "${WEB_APP_SOURCE_DIR}" ${NPM_EXECUTABLE} install COMMAND ${CMAKE_COMMAND} -E chdir "${WEB_APP_SOURCE_DIR}" ${NPM_EXECUTABLE} run build COMMENT "Building Web app with npm" WORKING_DIRECTORY "${WEB_APP_SOURCE_DIR}" VERBATIM ) message(STATUS "Web app can be built manually with: cmake --build . --target web-app") endif() # ============================================================ # macOS Packaging & Notarization # ============================================================ if(APPLE) # SETUP IDENTITIES set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") set(CPACK_PACKAGE_VENDOR "Lemonade") set(CPACK_PACKAGE_CONTACT "lemonade@amd.com") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Lemonade Local LLM Server") set(CPACK_PACKAGE_DESCRIPTION "A lightweight, high-performance local LLM server.") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/../../LICENSE") # Skip readme page entirely set(CPACK_INSTALL_README_DIR "") if(NOT DEFINED SIGNING_IDENTITY) if(DEFINED ENV{DEVELOPER_ID_APPLICATION_IDENTITY}) set(SIGNING_IDENTITY "$ENV{DEVELOPER_ID_APPLICATION_IDENTITY}") else() set(SIGNING_IDENTITY "BBC1E1924FF481293CF0928B5DFFF8A6419D7DA0") endif() endif() if(NOT DEFINED INSTALLER_IDENTITY) if(DEFINED ENV{DEVELOPER_ID_INSTALLER_IDENTITY}) set(INSTALLER_IDENTITY "$ENV{DEVELOPER_ID_INSTALLER_IDENTITY}") else() set(INSTALLER_IDENTITY "B69167355F20D364EE8FD7126D767B93B5344EBB") endif() endif() # Configure Plists & Entitlements configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/com.lemonade.server.plist.in ${CMAKE_CURRENT_BINARY_DIR}/com.lemonade.server.plist @ONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/com.lemonade.tray.plist.in ${CMAKE_CURRENT_BINARY_DIR}/com.lemonade.tray.plist @ONLY) set(ENTITLEMENTS_PATH "${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/server/entitlements.plist") if(NOT EXISTS "${ENTITLEMENTS_PATH}") file(WRITE "${ENTITLEMENTS_PATH}" " com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-library-validation com.apple.security.cs.allow-dyld-environment-variables ") endif() endif() # Add tray application subdirectory add_subdirectory(src/cpp/tray) if(APPLE) # SIGNING LOGIC (Release Only) if(CMAKE_BUILD_TYPE STREQUAL "Release") message(STATUS "Release Build: Configuring Hardened Runtime Signing for Router") # Sign 'lemonade-router' add_custom_command(TARGET ${EXECUTABLE_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E echo "--- Signing ${EXECUTABLE_NAME} ---" COMMAND codesign --force --options runtime --timestamp --entitlements "${ENTITLEMENTS_PATH}" --sign "${SIGNING_IDENTITY}" -v $ COMMENT "Signing ${EXECUTABLE_NAME} with Hardened Runtime" VERBATIM ) # Note: 'lemonade-server' signs itself in tray/CMakeLists.txt endif() # Prepare Electron App set(ELECTRON_BUILD_COPY "${CMAKE_BINARY_DIR}/Lemonade.app") add_custom_target(prepare_electron_app COMMAND ${CMAKE_COMMAND} -E remove_directory "${ELECTRON_BUILD_COPY}" COMMAND cp -R "${ELECTRON_APP_BUILD_DIR}/mac-arm64/Lemonade.app" "${CMAKE_BINARY_DIR}/" || cp -R "${ELECTRON_APP_BUILD_DIR}/mac/Lemonade.app" "${CMAKE_BINARY_DIR}/" || cp -R "${ELECTRON_APP_BUILD_DIR}/Lemonade.app" "${CMAKE_BINARY_DIR}/" || echo "Warning: Could not find Lemonade.app in ${ELECTRON_APP_BUILD_DIR}" COMMENT "Copying Electron App..." DEPENDS electron-app ) set(CPACK_PRE_BUILD_SCRIPTS "${CMAKE_BINARY_DIR}/cpack_prebuild.cmake") file(WRITE "${CMAKE_BINARY_DIR}/cpack_prebuild.cmake" "message(STATUS \"Pre-build: Ensuring Electron app is prepared...\")\n" "execute_process(COMMAND ${CMAKE_COMMAND} --build \"${CMAKE_BINARY_DIR}\" --target prepare_electron_app)\n" ) # INSTALLATION (Component Structure) # Main Router -> /usr/local/bin install(TARGETS ${EXECUTABLE_NAME} RUNTIME DESTINATION "bin" COMPONENT Runtime) # Sign the router binary after installation (Release builds only) if(CMAKE_BUILD_TYPE STREQUAL "Release") install(CODE " execute_process( COMMAND codesign --force --options runtime --timestamp --entitlements \"${ENTITLEMENTS_PATH}\" --sign \"${SIGNING_IDENTITY}\" -v \"${CMAKE_BINARY_DIR}/_CPack_Packages/Darwin/productbuild/Lemonade-${PROJECT_VERSION}-Darwin/Runtime/usr/local/bin/${EXECUTABLE_NAME}\" RESULT_VARIABLE SIGN_RESULT ) if(NOT SIGN_RESULT EQUAL 0) message(FATAL_ERROR \"Failed to sign ${EXECUTABLE_NAME}\") endif() " COMPONENT Runtime) endif() # Tray/Server -> /usr/local/bin # (Since tray/CMakeLists.txt skips install on Apple, we must do it here) if(TARGET lemonade-server) install(TARGETS lemonade-server RUNTIME DESTINATION "bin" COMPONENT Runtime) # Sign the server binary after installation (Release builds only) if(CMAKE_BUILD_TYPE STREQUAL "Release") install(CODE " execute_process( COMMAND codesign --force --options runtime --timestamp --entitlements \"${ENTITLEMENTS_PATH}\" --sign \"${SIGNING_IDENTITY}\" -v \"${CMAKE_BINARY_DIR}/_CPack_Packages/Darwin/productbuild/Lemonade-${PROJECT_VERSION}-Darwin/Runtime/usr/local/bin/lemonade-server\" RESULT_VARIABLE SIGN_RESULT ) if(NOT SIGN_RESULT EQUAL 0) message(FATAL_ERROR \"Failed to sign lemonade-server\") endif() " COMPONENT Runtime) endif() endif() # Resources -> /Library/Application Support/Lemonade/resources install(DIRECTORY ${CMAKE_BINARY_DIR}/resources/ DESTINATION "/Library/Application Support/Lemonade/resources" COMPONENT Resources) # App Bundle -> /Applications install(DIRECTORY "${ELECTRON_BUILD_COPY}" DESTINATION "/Applications" USE_SOURCE_PERMISSIONS COMPONENT Applications ) # Plists -> /Library/... install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.lemonade.server.plist" DESTINATION "/Library/LaunchDaemons" COMPONENT Runtime) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.lemonade.tray.plist" DESTINATION "/Library/LaunchAgents" COMPONENT Runtime) # Placeholder -> /usr/local/share/lemonade-server file(WRITE "${CMAKE_BINARY_DIR}/.keep" "") install(FILES "${CMAKE_BINARY_DIR}/.keep" DESTINATION "usr/local/share/lemonade-server" COMPONENT Runtime) # ackaging & Notarization set(CPACK_GENERATOR "productbuild") set(CPACK_PACKAGE_NAME "Lemonade") set(CPACK_PRODUCTBUILD_IDENTIFIER "com.lemonade.server") set(CPACK_PRODUCTBUILD_DOMAIN "system") if(INSTALLER_IDENTITY) set(CPACK_PRODUCTBUILD_IDENTITY_NAME "${INSTALLER_IDENTITY}") endif() set(CPACK_PRODUCTBUILD_RELOCATE OFF) # Prevent install_name_tool from invalidating signatures set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE) set(CPACK_PRODUCTBUILD_BUNDLE com.lemonade.server.Applications OFF) set(CPACK_PRODUCTBUILD_BUNDLE_ID com.lemonade.server.Applications) set(CPACK_PACKAGING_INSTALL_PREFIX "/usr/local") set(CPACK_SET_DESTDIR ON) set(CPACK_PACKAGE_RELOCATABLE OFF) set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) set(CPACK_COMPONENT_APPLICATIONS_IS_ABSOLUTE ON) # license file extension for CPack configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" "${CMAKE_BINARY_DIR}/LICENSE.txt" COPYONLY) set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_BINARY_DIR}/LICENSE.txt") set(CPACK_POSTFLIGHT_RUNTIME_SCRIPT "${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/postinst-full-mac") include(CPack) set(PACKAGE_DEPENDS prepare_electron_app ${EXECUTABLE_NAME}) add_custom_target(package-macos DEPENDS ${PACKAGE_DEPENDS} COMMAND ${CMAKE_COMMAND} --build . --config $ --target package COMMENT "Running CPack to create signed installer..." VERBATIM ) find_program(XCRUN_EXE xcrun) if(XCRUN_EXE) set(PKG_NAME "Lemonade-${PROJECT_VERSION}-Darwin.pkg") add_custom_target(notarize COMMAND ${CMAKE_COMMAND} -E echo "Submitting ${PKG_NAME} for notarization..." COMMAND ${XCRUN_EXE} notarytool submit "${CMAKE_BINARY_DIR}/${PKG_NAME}" --keychain-profile "AC_PASSWORD" --wait --verbose COMMAND ${CMAKE_COMMAND} -E echo "Stapling ticket to ${PKG_NAME}..." COMMAND ${XCRUN_EXE} stapler staple "${CMAKE_BINARY_DIR}/${PKG_NAME}" WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "Notarizing and Stapling macOS package..." VERBATIM ) add_dependencies(notarize package-macos) endif() endif() # ============================================================ # CPack Configuration for .deb Package (Linux only) # ============================================================ if(UNIX AND NOT APPLE) set(CPACK_GENERATOR "DEB") set(CPACK_SET_DESTDIR ON) set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") set(CPACK_PACKAGE_VENDOR "Lemonade") set(CPACK_PACKAGE_CONTACT "lemonade@amd.com") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Lemonade Local LLM Server") set(CPACK_PACKAGE_DESCRIPTION "A lightweight, high-performance local LLM server with support for multiple backends including llama.cpp, FastFlowLM, and RyzenAI.") # Debian-specific settings set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Lemonade Team ") set(CPACK_DEBIAN_PACKAGE_SECTION "utils") set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional") set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") # Installation paths - use ~/.local for user install (no sudo needed) # Set via environment: cmake -DCPACK_PACKAGING_INSTALL_PREFIX=$HOME/.local .. # Default to /opt to match the install prefix used by the project. if(NOT DEFINED CPACK_PACKAGING_INSTALL_PREFIX) set(CPACK_PACKAGING_INSTALL_PREFIX "/opt") endif() # Only install our executables (not curl/development files) # Note: lemonade-server installation is handled in tray/CMakeLists.txt install(TARGETS ${EXECUTABLE_NAME} RUNTIME DESTINATION bin ) # Install resources (all files including KaTeX fonts) install(DIRECTORY ${CMAKE_BINARY_DIR}/resources/ DESTINATION share/lemonade-server/resources ) # Install example scripts install(DIRECTORY ${CMAKE_SOURCE_DIR}/examples/ DESTINATION share/lemonade-server/examples USE_SOURCE_PERMISSIONS PATTERN "*.sh" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) # Configure systemd service file with correct paths configure_file( ${CMAKE_SOURCE_DIR}/data/lemonade-server.service.in ${CMAKE_BINARY_DIR}/lemonade-server.service @ONLY ) # Install systemd service and configuration files install(FILES ${CMAKE_BINARY_DIR}/lemonade-server.service DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/systemd/system ) # Create symlink in standard systemd search path only if not installing to /usr # (if prefix is /usr, the service is already in the standard location) if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") install(CODE " file(MAKE_DIRECTORY \"\$ENV{DESTDIR}/usr/lib/systemd/system\") execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/lib/systemd/system/lemonade-server.service \"\$ENV{DESTDIR}/usr/lib/systemd/system/lemonade-server.service\" ) ") endif() install(FILES ${CMAKE_SOURCE_DIR}/data/lemonade.conf DESTINATION /etc/lemonade PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE WORLD_READ ) install(FILES ${CMAKE_SOURCE_DIR}/data/secrets.conf DESTINATION /etc/lemonade PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ GROUP_WRITE ) # Create symlinks for KaTeX fonts if they exist on the system # Check if fonts-katex package fonts are available set(KATEX_FONTS_DIR "/usr/share/fonts/truetype/katex") if(EXISTS "${KATEX_FONTS_DIR}") message(STATUS "KaTeX fonts found at ${KATEX_FONTS_DIR}, creating symlinks") # Ensure web-app directory exists in build directory file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/resources/web-app") set(KATEX_FONTS "KaTeX_AMS-Regular.ttf" "KaTeX_AMS-Regular.woff" "KaTeX_AMS-Regular.woff2" "KaTeX_Caligraphic-Bold.ttf" "KaTeX_Caligraphic-Bold.woff" "KaTeX_Caligraphic-Bold.woff2" "KaTeX_Caligraphic-Regular.ttf" "KaTeX_Caligraphic-Regular.woff" "KaTeX_Caligraphic-Regular.woff2" "KaTeX_Fraktur-Bold.ttf" "KaTeX_Fraktur-Bold.woff" "KaTeX_Fraktur-Bold.woff2" "KaTeX_Fraktur-Regular.ttf" "KaTeX_Fraktur-Regular.woff" "KaTeX_Fraktur-Regular.woff2" "KaTeX_Main-Bold.ttf" "KaTeX_Main-Bold.woff" "KaTeX_Main-Bold.woff2" "KaTeX_Main-BoldItalic.ttf" "KaTeX_Main-BoldItalic.woff" "KaTeX_Main-BoldItalic.woff2" "KaTeX_Main-Italic.ttf" "KaTeX_Main-Italic.woff" "KaTeX_Main-Italic.woff2" "KaTeX_Main-Regular.ttf" "KaTeX_Main-Regular.woff" "KaTeX_Main-Regular.woff2" "KaTeX_Math-BoldItalic.ttf" "KaTeX_Math-BoldItalic.woff" "KaTeX_Math-BoldItalic.woff2" "KaTeX_Math-Italic.ttf" "KaTeX_Math-Italic.woff" "KaTeX_Math-Italic.woff2" "KaTeX_SansSerif-Bold.ttf" "KaTeX_SansSerif-Bold.woff" "KaTeX_SansSerif-Bold.woff2" "KaTeX_SansSerif-Italic.ttf" "KaTeX_SansSerif-Italic.woff" "KaTeX_SansSerif-Italic.woff2" "KaTeX_SansSerif-Regular.ttf" "KaTeX_SansSerif-Regular.woff" "KaTeX_SansSerif-Regular.woff2" "KaTeX_Script-Regular.ttf" "KaTeX_Script-Regular.woff" "KaTeX_Script-Regular.woff2" "KaTeX_Size1-Regular.ttf" "KaTeX_Size1-Regular.woff" "KaTeX_Size1-Regular.woff2" "KaTeX_Size2-Regular.ttf" "KaTeX_Size2-Regular.woff" "KaTeX_Size2-Regular.woff2" "KaTeX_Size3-Regular.ttf" "KaTeX_Size3-Regular.woff" "KaTeX_Size3-Regular.woff2" "KaTeX_Size4-Regular.ttf" "KaTeX_Size4-Regular.woff" "KaTeX_Size4-Regular.woff2" "KaTeX_Typewriter-Regular.ttf" "KaTeX_Typewriter-Regular.woff" "KaTeX_Typewriter-Regular.woff2" ) foreach(font ${KATEX_FONTS}) set(FONT_LINK "${CMAKE_BINARY_DIR}/resources/web-app/${font}") set(FONT_TARGET "${KATEX_FONTS_DIR}/${font}") # Only create symlink if the target font exists if(EXISTS "${FONT_TARGET}") # Remove existing file/symlink if present if(EXISTS "${FONT_LINK}" OR IS_SYMLINK "${FONT_LINK}") file(REMOVE "${FONT_LINK}") endif() # Create symlink in build directory file(CREATE_LINK "${FONT_TARGET}" "${FONT_LINK}" SYMBOLIC) endif() endforeach() else() message(STATUS "KaTeX fonts not found at ${KATEX_FONTS_DIR}, using bundled fonts") endif() # Check if Electron app is available for full package option(BUILD_ELECTRON_APP "Build and include Electron app in deb package" OFF) # Build electron app if requested and target is available if(BUILD_ELECTRON_APP AND TARGET electron-app) message(STATUS "BUILD_ELECTRON_APP is ON - building Electron app...") execute_process( COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target electron-app RESULT_VARIABLE ELECTRON_BUILD_RESULT ) if(NOT ELECTRON_BUILD_RESULT EQUAL 0) message(FATAL_ERROR "Failed to build Electron app") endif() endif() if(BUILD_ELECTRON_APP AND EXISTS "${ELECTRON_APP_UNPACKED_DIR}/${ELECTRON_EXE_NAME}") message(STATUS "Electron app found at ${ELECTRON_APP_UNPACKED_DIR}") message(STATUS "Building full deb package with Electron app") # Full package name set(CPACK_PACKAGE_NAME "lemonade-server") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl4, libssl3, libz1, unzip, libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libsecret-1-0, libasound2t64, fonts-katex") set(CPACK_DEBIAN_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb") # Install Electron app install(DIRECTORY "${ELECTRON_APP_UNPACKED_DIR}/" DESTINATION share/lemonade-server/app USE_SOURCE_PERMISSIONS PATTERN "*.so" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) if (NOT WIN32 AND NOT APPLE) if (EXISTS "${ELECTRON_APP_UNPACKED_DIR}/chrome-sandbox") install(FILES "${ELECTRON_APP_UNPACKED_DIR}/chrome-sandbox" DESTINATION share/lemonade-server/app PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE SETUID ) endif() if (EXISTS "${ELECTRON_APP_UNPACKED_DIR}/chrome_crashpad_handler") install(FILES "${ELECTRON_APP_UNPACKED_DIR}/chrome_crashpad_handler" DESTINATION share/lemonade-server/app PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE ) endif() endif() if (NOT WIN32 AND NOT APPLE) # Install .desktop file for electron app install(FILES ${CMAKE_SOURCE_DIR}/data/lemonade-app.desktop DESTINATION share/applications ) # Create symlink in standard applications path only if not installing to /usr if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") install(CODE " file(MAKE_DIRECTORY \"\$ENV{DESTDIR}/usr/share/applications\") execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/share/applications/lemonade-app.desktop \"\$ENV{DESTDIR}/usr/share/applications/lemonade-app.desktop\" ) ") endif() install(FILES ${CMAKE_SOURCE_DIR}/src/app/assets/logo.svg DESTINATION share/pixmaps RENAME lemonade-app.svg ) # Create symlink in standard pixmaps path only if not installing to /usr if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") install(CODE " file(MAKE_DIRECTORY \"\$ENV{DESTDIR}/usr/share/pixmaps\") execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink ${CMAKE_INSTALL_PREFIX}/share/pixmaps/lemonade-app.svg \"\$ENV{DESTDIR}/usr/share/pixmaps/lemonade-app.svg\" ) ") endif() # Create symlink in standard bin path only if not installing to /usr if(NOT CMAKE_INSTALL_PREFIX STREQUAL "/usr") install(CODE " file(MAKE_DIRECTORY \"\$ENV{DESTDIR}/usr/bin\") execute_process( COMMAND ${CMAKE_COMMAND} -E create_symlink \"\${CMAKE_INSTALL_PREFIX}/share/lemonade-server/app/lemonade\" \"\$ENV{DESTDIR}/usr/bin/lemonade-app\" ) ") else() install(CODE " file(CREATE_LINK \"\${CMAKE_INSTALL_PREFIX}/share/lemonade-server/app/lemonade\" \"\${CMAKE_INSTALL_PREFIX}/bin/lemonade-app\" SYMBOLIC) ") endif() endif() else() set(CPACK_PACKAGE_NAME "lemonade-server") set(CPACK_DEBIAN_PACKAGE_DEPENDS "libcurl4, libssl3, libz1, unzip, fonts-katex") set(CPACK_DEBIAN_PACKAGE_RECOMMENDS "chromium-browser | google-chrome-stable | chromium") set(CPACK_DEBIAN_FILE_NAME "${CPACK_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}_${CPACK_DEBIAN_PACKAGE_ARCHITECTURE}.deb") endif() # Control scripts for debian package set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/postinst;${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/prerm;${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/postrm") # RPM specific variables defined within include(${CMAKE_CURRENT_SOURCE_DIR}/src/cpp/CPackRPM.cmake) include(CPack) # ============================================================ # AppImage Build Target (Linux only) # ============================================================ if(NODE_EXECUTABLE AND NPM_EXECUTABLE) # Set the build directory and source directory for AppImage set(APPIMAGE_BUILD_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/app-appimage-src") set(APPIMAGE_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/app-appimage") add_custom_target(appimage COMMAND ${CMAKE_COMMAND} -E echo "Copying Electron app source to AppImage build directory..." COMMAND ${CMAKE_COMMAND} -E remove_directory "${APPIMAGE_BUILD_SOURCE_DIR}" COMMAND ${CMAKE_COMMAND} -E make_directory "${APPIMAGE_BUILD_SOURCE_DIR}" COMMAND ${CMAKE_COMMAND} -E copy_directory "${ELECTRON_APP_SOURCE_DIR}" "${APPIMAGE_BUILD_SOURCE_DIR}" COMMAND ${CMAKE_COMMAND} -E echo "Setting version to ${PROJECT_VERSION}..." COMMAND ${CMAKE_COMMAND} -E chdir "${APPIMAGE_BUILD_SOURCE_DIR}" ${NPM_COMMAND} version ${PROJECT_VERSION} --no-git-tag-version --allow-same-version COMMAND ${CMAKE_COMMAND} -E echo "Installing npm dependencies for AppImage build..." COMMAND ${CMAKE_COMMAND} -E chdir "${APPIMAGE_BUILD_SOURCE_DIR}" ${NPM_COMMAND} ci COMMAND ${CMAKE_COMMAND} -E echo "Building AppImage with electron-builder..." COMMAND ${CMAKE_COMMAND} -E chdir "${APPIMAGE_BUILD_SOURCE_DIR}" ${NPM_COMMAND} run build:renderer:prod COMMAND ${CMAKE_COMMAND} -E chdir "${APPIMAGE_BUILD_SOURCE_DIR}" ${NPM_EXECUTABLE} exec -- electron-builder --linux AppImage --config.directories.output=${APPIMAGE_BUILD_DIR} COMMAND ${CMAKE_COMMAND} -E echo "AppImage build complete. Output in ${APPIMAGE_BUILD_DIR}" COMMENT "Building Electron app as AppImage" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" ) message(STATUS "AppImage build target available: cmake --build . --target appimage") else() message(STATUS "Node.js or npm not found - AppImage build target disabled") endif() endif() lemonade-sdk-lemonade-d88f5d9/CMakePresets.json000066400000000000000000000036451515430344400215300ustar00rootroot00000000000000{ "version": 5, "vendor": { "lemonade": {} }, "configurePresets": [ { "name": "default", "displayName": "Default (Ninja)", "description": "Build with Ninja generator", "generator": "Ninja", "binaryDir": "${sourceDir}/build", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } }, { "name": "windows", "displayName": "Windows (Visual Studio 2022)", "description": "Build with Visual Studio 2022 generator on Windows", "generator": "Visual Studio 17 2022", "architecture": "x64", "binaryDir": "${sourceDir}/build" }, { "name": "vs18", "displayName": "Windows (Visual Studio 2026)", "description": "Build with Visual Studio 2026 generator on Windows", "generator": "Visual Studio 18 2026", "architecture": "x64", "binaryDir": "${sourceDir}/build" }, { "name": "debug", "displayName": "Debug (Ninja)", "description": "Build with Ninja generator in Debug mode", "generator": "Ninja", "binaryDir": "${sourceDir}/build-debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { "name": "release", "displayName": "Release (Ninja)", "description": "Build with Ninja generator in Release mode", "generator": "Ninja", "binaryDir": "${sourceDir}/build-release", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }, "hidden": true } ], "buildPresets": [ { "name": "default", "configurePreset": "default" }, { "name": "windows", "configurePreset": "windows", "configuration": "Release" }, { "name": "vs18", "configurePreset": "vs18", "configuration": "Release" }, { "name": "debug", "configurePreset": "debug" }, { "name": "release", "configurePreset": "release" } ] } lemonade-sdk-lemonade-d88f5d9/Dockerfile000066400000000000000000000040771515430344400203010ustar00rootroot00000000000000# ============================================================== # # 1. Build stage — compile lemonade C++ binaries # # ============================================================ FROM ubuntu:24.04 AS builder # Avoid interactive prompts during build ENV DEBIAN_FRONTEND=noninteractive # Install build dependencies RUN apt-get update && apt-get install -y \ build-essential \ cmake \ ninja-build \ libssl-dev \ pkg-config \ libdrm-dev \ git \ && rm -rf /var/lib/apt/lists/* # Copy source code COPY . /app WORKDIR /app # Build the project RUN rm -rf build && \ cmake --preset default && \ cmake --build --preset default # Debug: Check build outputs RUN echo "=== Build directory contents ===" && \ ls -la build/ && \ echo "=== Checking for resources ===" && \ find build/ -name "*.json" -o -name "resources" -type d # # ============================================================ # # 2. Runtime stage — small, clean image # # ============================================================ FROM ubuntu:24.04 # Install runtime dependencies only RUN apt-get update && apt-get install -y \ libcurl4 \ curl \ libssl3 \ zlib1g \ libdrm2 \ vulkan-tools \ libvulkan1 \ unzip \ libgomp1 \ && rm -rf /var/lib/apt/lists/* # Create application directory WORKDIR /opt/lemonade # Copy built executables and resources from builder COPY --from=builder /app/build/lemonade-router ./lemonade-router COPY --from=builder /app/build/lemonade-server ./lemonade-server COPY --from=builder /app/build/resources ./resources # Make executables executable RUN chmod +x ./lemonade-router ./lemonade-server # Create necessary directories RUN mkdir -p /opt/lemonade/llama/cpu \ /opt/lemonade/llama/vulkan \ /root/.cache/huggingface # Expose default port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/live || exit 1 # Default command: start server in headless mode CMD ["./lemonade-server", "serve", "--no-tray", "--host", "0.0.0.0"] lemonade-sdk-lemonade-d88f5d9/LICENSE000066400000000000000000000261351515430344400173130ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. lemonade-sdk-lemonade-d88f5d9/NOTICE.md000066400000000000000000000172701515430344400176110ustar00rootroot00000000000000PORTIONS LICENSED AS FOLLOWS ## Lucide Icons SVG icon paths copied from Lucide (https://lucide.dev) are used in `src/app/src/renderer/components/Icons.tsx`. The following icons are included: ChevronLeft, ChevronRight, BookOpen, ExternalLink, Boxes, Cpu, Store, Settings, and SlidersHorizontal. These icons are licensed under the ISC license from https://github.com/lucide-icons/lucide. > ISC License > > Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part of Feather (MIT). All other copyright (c) for Lucide are held by Lucide Contributors 2022. > > Permission to use, copy, modify, and/or distribute this software for any > purpose with or without fee is hereby granted, provided that the above > copyright notice and this permission notice appear in all copies. > > THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ## amdxdna_accel.h The file `src/cpp/include/lemon/amdxdna_accel.h` is licensed under GPL-2.0 WITH Linux-syscall-note. The Linux-syscall-note exception allows this GPL-licensed kernel UAPI header to be included in userspace applications without imposing GPL requirements on the application itself. This exception is standard for Linux kernel headers that define the userspace API for system calls and ioctl operations. ## llama.cpp Binaries for llama.cpp are downloaded under the MIT license from https://github.com/ggml-org/llama.cpp, as well as https://github.com/lemonade-sdk/llamacpp-rocm (which uses https://github.com/ggml-org/llama.cpp to build them.) Lemonade SDK used the [ONNX TurnkeyML](https://github.com/onnx/turnkeyml) project as a starting point under the [Apache 2.0 license](./LICENSE). > MIT License > > Copyright (c) 2023-2024 The ggml authors > > 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. ## Ollama Lemonade's Ollama-compatible API is based on the [Ollama API specification](https://github.com/ollama/ollama/blob/main/docs/api.md), licensed under the MIT license from https://github.com/ollama/ollama. > MIT License > > Copyright (c) Ollama > > 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. ## TurnkeyML Attribution TurnkeyML used code from other open source projects as a starting point (see [NOTICE.md](NOTICE.md)). Thank you Philip Colangelo, Derek Elkins, Jeremy Fowers, Dan Gard, Victoria Godsoe, Mark Heaps, Daniel Holanda, Brian Kurtz, Mariah Larwood, Philip Lassen, Andrew Ling, Adrian Macias, Gary Malik, Sarah Massengill, Ashwin Murthy, Hatice Ozen, Tim Sears, Sean Settle, Krishna Sivakumar, Aviv Weinstein, Xueli Xao, Bill Xing, and Lev Zlotnik for your contributions to that work. \> TurnkeyML used code from the [MLAgility](https://github.com/groq/mlagility) and [GroqFlow](https://github.com/groq/groqflow) projects as a starting point. Much of that code was refactored, improved, or replaced by the time TurnkeyML was published. \> TurnkeyML uses the [Microsoft lemon emoji](https://github.com/microsoft/fluentui-emoji) as an icon for the lemoande tool. >The MIT License > >Copyright 2023 Groq Inc. > >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. ## aixlog aixlog is a header-only C++ logging library, licensed under the MIT license from https://github.com/badaix/aixlog. It has been imported as is with no modifications in the lemonade tree under the MIT license. It's purpose is a logging system. > MIT License > > Copyright (C) 2017-2021 Johannes Pohl > > 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. lemonade-sdk-lemonade-d88f5d9/README.md000066400000000000000000000372631515430344400175710ustar00rootroot00000000000000## 🍋 Lemonade: Refreshingly fast local LLMs, Image and Speech Generation

Discord Lemonade Server Build Windows 11 Ubuntu 24.04 | 25.04 macOS (beta) Get it from the Snap Store Arch Linux Made with Python PRs Welcome Latest Release GitHub downloads GitHub issues License: Apache Code style: black Star History Chart

Lemonade Banner

Download | Documentation | Discord

Lemonade helps users discover and run local AI apps by serving optimized LLMs, images, and speech right from their own GPUs and NPUs. Apps like [n8n](https://n8n.io/integrations/lemonade-model/), [VS Code Copilot](https://marketplace.visualstudio.com/items?itemName=lemonade-sdk.lemonade-sdk), [Morphik](https://www.morphik.ai/docs/local-inference#lemonade), and many more use Lemonade to seamlessly run generative AI on any PC. ## Getting Started 1. **Install**: [Windows](https://lemonade-server.ai/install_options.html#windows) · [Linux](https://lemonade-server.ai/install_options.html#linux) · [macOS (beta)](https://lemonade-server.ai/install_options.html#macos) · [Docker](https://lemonade-server.ai/install_options.html#docker) · [Source](./docs/dev-getting-started.md) 2. **Get Models**: Browse and download with the [Model Manager](#model-library) 3. **Generate**: Try models with the built-in interfaces for chat, image gen, speech gen, and more 4. **Mobile**: Take your lemonade to go: [iOS](https://apps.apple.com/us/app/lemonade-mobile/id6757372210) · [Android](https://play.google.com/store/apps/details?id=com.lemonade.mobile.chat.ai&pli=1) · [Source](https://github.com/lemonade-sdk/lemonade-mobile) 5. **Connect**: Use Lemonade with your favorite apps:

Continue  Deep Tutor  Dify  Gaia  GitHub Copilot  Infinity Arcade  Iterate.ai  n8n  Open WebUI  OpenHands

View all apps →
Want your app featured here? Just submit a marketplace PR!

## Using the CLI To run and chat with Gemma 3: ``` lemonade-server run Gemma-3-4b-it-GGUF ``` More modalities: ``` # image gen lemonade-server run SDXL-Turbo # speech gen lemonade-server run kokoro-v1 # transcription lemonade-server run Whisper-Large-v3-Turbo ``` To see models availables and download them: ``` lemonade-server list lemonade-server pull Gemma-3-4b-it-GGUF ``` To see the backends available on your PC: ``` lemonade-server recipes ``` ## Model Library Model Manager Lemonade supports a wide variety of LLMs (**GGUF**, **FLM**, and **ONNX**), whisper, stable diffusion, etc. models across CPU, GPU, and NPU. Use `lemonade-server pull` or the built-in **Model Manager** to download models. You can also import custom GGUF/ONNX models from Hugging Face. **[Browse all built-in models →](https://lemonade-server.ai/models.html)**
## Supported Configurations Lemonade supports multiple recipes (LLM, speech, TTS, and image generation), and each recipe has its own backend and hardware requirements.
Modality Recipe Backend Device OS
Text generation llamacpp vulkan GPU Windows, Linux
rocm Select AMD GPUs* Windows, Linux
cpu x86_64 Windows, Linux
metal Apple Silicon GPU macOS (beta)
flm npu XDNA2 NPU Windows
ryzenai-llm npu XDNA2 NPU Windows
Speech-to-text whispercpp npu XDNA2 NPU Windows
cpu x86_64 Windows
Text-to-speech kokoro cpu x86_64 Windows, Linux
Image generation sd-cpp rocm Selected AMD GPUs Windows, Linux
cpu x86_64 CPU Windows, Linux
To check exactly which recipes/backends are supported on your own machine, run: ``` lemonade-server recipes ```
* See supported AMD ROCm platforms
Architecture Platform Support GPU Models
gfx1151 (STX Halo) Windows, Ubuntu Ryzen AI MAX+ Pro 395
gfx120X (RDNA4) Windows, Ubuntu Radeon AI PRO R9700, RX 9070 XT/GRE/9070, RX 9060 XT
gfx110X (RDNA3) Windows, Ubuntu Radeon PRO W7900/W7800/W7700/V710, RX 7900 XTX/XT/GRE, RX 7800 XT, RX 7700 XT
## Project Roadmap | Under Development | Under Consideration | Recently Completed | |---------------------------|-----------------------------|------------------------| | MLX support | vLLM support | macOS (beta) | | More whisper.cpp backends | Enhanced custom model usage | Image generation | | More SD.cpp backends | | Speech-to-text | | | | Text-to-speech | | | | Apps marketplace | ## Integrate Lemonade Server with Your Application You can use any OpenAI-compatible client library by configuring it to use `http://localhost:8000/api/v1` as the base URL. A table containing official and popular OpenAI clients on different languages is shown below. Feel free to pick and choose your preferred language. | Python | C++ | Java | C# | Node.js | Go | Ruby | Rust | PHP | |--------|-----|------|----|---------|----|-------|------|-----| | [openai-python](https://github.com/openai/openai-python) | [openai-cpp](https://github.com/olrea/openai-cpp) | [openai-java](https://github.com/openai/openai-java) | [openai-dotnet](https://github.com/openai/openai-dotnet) | [openai-node](https://github.com/openai/openai-node) | [go-openai](https://github.com/sashabaranov/go-openai) | [ruby-openai](https://github.com/alexrudall/ruby-openai) | [async-openai](https://github.com/64bit/async-openai) | [openai-php](https://github.com/openai-php/client) | ### Python Client Example ```python from openai import OpenAI # Initialize the client to use Lemonade Server client = OpenAI( base_url="http://localhost:8000/api/v1", api_key="lemonade" # required but unused ) # Create a chat completion completion = client.chat.completions.create( model="Llama-3.2-1B-Instruct-Hybrid", # or any other available model messages=[ {"role": "user", "content": "What is the capital of France?"} ] ) # Print the response print(completion.choices[0].message.content) ``` For more detailed integration instructions, see the [Integration Guide](./docs/server/server_integration.md). ## FAQ To read our frequently asked questions, see our [FAQ Guide](./docs/faq.md) ## Contributing We are actively seeking collaborators from across the industry. If you would like to contribute to this project, please check out our [contribution guide](./docs/contribute.md). New contributors can find beginner-friendly issues tagged with "Good First Issue" to get started. Good First Issue ## Maintainers This is a community project maintained by @amd-pworfolk @bitgamma @danielholanda @jeremyfowers @Geramy @ramkrishna2910 @siavashhub @sofiageo @superm1 @vgodsoe, and sponsored by AMD. You can reach us by filing an [issue](https://github.com/lemonade-sdk/lemonade/issues), emailing [lemonade@amd.com](mailto:lemonade@amd.com), or joining our [Discord](https://discord.gg/5xXzkMu8Zk). ## Code Signing Policy Free code signing provided by [SignPath.io](https://signpath.io), certificate by [SignPath Foundation](https://signpath.org). - **Committers and reviewers**: [Maintainers](#maintainers) of this repo - **Approvers**: [Owners](https://github.com/orgs/lemonade-sdk/people?query=role%3Aowner) **Privacy policy**: This program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it. When the user requests it, Lemonade downloads AI models from [Hugging Face Hub](https://huggingface.co/) (see their [privacy policy](https://huggingface.co/privacy)). ## License and Attribution This project is: - Built with C++ (server) and React (app) with ❤️ for the open source community, - Standing on the shoulders of great tools from: - [ggml/llama.cpp](https://github.com/ggml-org/llama.cpp) - [ggml/whisper.cpp](https://github.com/ggerganov/whisper.cpp) - [ggml/stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp) - [kokoros](https://github.com/lucasjinreal/Kokoros) - [OnnxRuntime GenAI](https://github.com/microsoft/onnxruntime-genai) - [Hugging Face Hub](https://github.com/huggingface/huggingface_hub) - [OpenAI API](https://github.com/openai/openai-python) - [IRON/MLIR-AIE](https://github.com/Xilinx/mlir-aie) - and more... - Accelerated by mentorship from the OCV Catalyst program. - Licensed under the [Apache 2.0 License](https://github.com/lemonade-sdk/lemonade/blob/main/LICENSE). - Portions of the project are licensed as described in [NOTICE.md](./NOTICE.md). lemonade-sdk-lemonade-d88f5d9/data/000077500000000000000000000000001515430344400172105ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/data/lemonade-app.desktop000066400000000000000000000004401515430344400231430ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Type=Application Name=Lemonade App Comment=Local LLMs with GPU and NPU acceleration - Desktop Application GenericName=AI Model Manager Exec=lemonade-app Icon=lemonade-app Terminal=false Categories=Development;Utility; Keywords=AI;LLM;GPU;NPU;Machine Learning; lemonade-sdk-lemonade-d88f5d9/data/lemonade-server.service.in000066400000000000000000000012211515430344400242630ustar00rootroot00000000000000[Unit] Description=Lemonade Server After=network-online.target [Service] Type=simple User=lemonade Group=lemonade WorkingDirectory=@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/lemonade EnvironmentFile=/etc/lemonade/lemonade.conf EnvironmentFile=/etc/lemonade/secrets.conf ExecStart=@CMAKE_INSTALL_FULL_BINDIR@/lemonade-server serve Restart=on-failure RestartSec=5s KillSignal=SIGINT LimitMEMLOCK=infinity # Security hardening PrivateTmp=yes NoNewPrivileges=yes ProtectSystem=full ProtectHome=yes ReadWritePaths=@CMAKE_INSTALL_FULL_LOCALSTATEDIR@/lib/lemonade RestrictRealtime=yes RestrictNamespaces=yes LockPersonality=yes [Install] WantedBy=multi-user.target lemonade-sdk-lemonade-d88f5d9/data/lemonade-web-app000066400000000000000000000015601515430344400222520ustar00rootroot00000000000000#!/bin/bash set -e LEMONADE_HOST="localhost" LEMONADE_PORT="8000" if [ -f /etc/lemonade/lemonade.conf ]; then source /etc/lemonade/lemonade.conf fi if [ -f ~/.config/lemonade/lemonade.conf ]; then source ~/.config/lemonade/lemonade.conf fi URL="http://${LEMONADE_HOST}:${LEMONADE_PORT}/" # Prefer chromium based browsers otherwise xdg-open if [ "$(echo "${LEMONADE_PREFER_CHROMIUM:-true}" | tr '[:upper:]' '[:lower:]')" = "true" ]; then if command -v google-chrome &> /dev/null; then google-chrome --app="$URL" elif command -v microsoft-edge-stable &> /dev/null; then microsoft-edge-stable --app="$URL" elif command -v chromium &> /dev/null; then chromium --app="$URL" elif command -v chromium-browser &> /dev/null; then chromium-browser --app="$URL" else xdg-open "$URL" fi else xdg-open "$URL" fi lemonade-sdk-lemonade-d88f5d9/data/lemonade-web-app.desktop000066400000000000000000000004421515430344400237200ustar00rootroot00000000000000[Desktop Entry] Version=1.0 Type=Application Name=Lemonade Web App Comment=Local LLMs with GPU and NPU acceleration - Web Interface GenericName=AI Model Manager Exec=lemonade-web-app Icon=lemonade-app Terminal=false Categories=Development;Utility; Keywords=AI;LLM;GPU;NPU;Machine Learning; lemonade-sdk-lemonade-d88f5d9/data/lemonade.conf000066400000000000000000000011011515430344400216340ustar00rootroot00000000000000#LEMONADE_HOST= #LEMONADE_PORT= #LEMONADE_LOG_LEVEL= #LEMONADE_LLAMACPP= #LEMONADE_CTX_SIZE= #LEMONADE_LLAMACPP_ARGS= #LEMONADE_EXTRA_MODELS_DIR= #LEMONADE_DISABLE_MODEL_FILTERING= #LEMONADE_ENABLE_DGPU_GTT= #LEMONADE_LLAMACPP_ROCM_BIN #LEMONADE_LLAMACPP_VULKAN_BIN= #LEMONADE_LLAMACPP_CPU_BIN= #LEMONADE_WHISPERCPP_CPU_BIN= #LEMONADE_WHISPERCPP_NPU_BIN= #LEMONADE_RYZENAI_SERVER_BIN= #LEMONADE_KOKORO_CPU_BIN= #LEMONADE_SDCPP_CPU_BIN= #LEMONADE_SDCPP_ROCM_BIN= #LEMONADE_SDCPP_VULKAN_BIN= #LEMONADE_LLAMACPP_PREFER_SYSTEM= #LEMONADE_NO_BROADCAST= #LEMONADE_MAX_LOADED_MODELS= lemonade-sdk-lemonade-d88f5d9/data/secrets.conf000066400000000000000000000000231515430344400215220ustar00rootroot00000000000000#LEMONADE_API_KEY= lemonade-sdk-lemonade-d88f5d9/docs/000077500000000000000000000000001515430344400172275ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/docs/CNAME000066400000000000000000000000231515430344400177700ustar00rootroot00000000000000lemonade-server.ai lemonade-sdk-lemonade-d88f5d9/docs/README.md000066400000000000000000000010541515430344400205060ustar00rootroot00000000000000# Lemonade SDK Documentation This documentation has moved to our main documentation site. ## Lemonade Server For the Lemonade Server (OpenAI-compatible HTTP server for local LLMs), see: **[Lemonade Server Documentation →](https://lemonade-server.ai/docs/)** ## lemonade-eval CLI For the `lemonade-eval` CLI (benchmarking, accuracy evaluation, and model preparation), see: **[lemonade-eval Documentation →](./eval/README.md)** lemonade-sdk-lemonade-d88f5d9/docs/assets/000077500000000000000000000000001515430344400205315ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/docs/assets/carousel.js000066400000000000000000000042651515430344400227130ustar00rootroot00000000000000// Simple YouTube video carousel for MkDocs Material document.addEventListener('DOMContentLoaded', function () { var carousel = document.getElementById('yt-carousel'); if (!carousel) return; // Support both data-ids (comma-separated) and data-videos (JSON array of {id, title}) var videos = []; if (carousel.dataset.videos) { try { videos = JSON.parse(carousel.dataset.videos); } catch (e) { console.error('Invalid JSON in data-videos:', e); } } else if (carousel.dataset.ids) { videos = carousel.dataset.ids.split(',').map(function(id) { return { id: id.trim(), title: '' }; }); } if (!videos.length) return; var idx = 0; function render() { var video = videos[idx]; var titleHtml = video.title ? `
${video.title}
` : ''; carousel.innerHTML = `
${titleHtml}
${idx+1} / ${videos.length}
`; document.getElementById('yt-prev').onclick = function() { idx = (idx - 1 + videos.length) % videos.length; render(); }; document.getElementById('yt-next').onclick = function() { idx = (idx + 1) % videos.length; render(); }; } render(); }); lemonade-sdk-lemonade-d88f5d9/docs/assets/common.css000066400000000000000000000124551515430344400225420ustar00rootroot00000000000000@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); :root { --docs-page-title-color: #1a1a1a; --docs-page-subtitle-color: #444444; --docs-side-text: #6b6148; --docs-side-text-strong: #3a301a; --docs-side-label: #6d613f; --docs-side-border: rgba(79, 66, 30, 0.18); --docs-side-surface: rgba(255, 255, 255, 0.55); --docs-side-surface-hover: rgba(255, 255, 255, 0.62); --docs-side-active-bg: rgba(46, 39, 25, 0.9); --docs-side-active-text: #fffef7; } /* Shared top navigation */ .navbar, .navbar a, .navbar .brand-title { font-family: 'Inter', 'Segoe UI', 'Arial', sans-serif; } /* Shared non-index page background */ #models-page-body, #news-page-body, #marketplace-body { background: radial-gradient(circle at top, rgba(255, 237, 172, 0.12), transparent 42%), #fffbe6 !important; } /* Remove pre-footer hairline on docs subpages */ #models-page-body .site-footer, #news-page-body .site-footer, #marketplace-body .site-footer { border-top: none; } /* Shared non-homepage heading system (matches news style) */ .docs-page-hero { text-align: center; } .docs-page-title { margin: 0 0 16px; font-size: 3.5rem; font-weight: 400; color: var(--docs-page-title-color); line-height: 1.05; letter-spacing: -0.03em; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .docs-page-subtitle { margin: 0 auto; max-width: 700px; font-size: 1.125rem; font-weight: 500; font-family: 'Inter', 'Segoe UI', 'Arial', sans-serif; color: var(--docs-page-subtitle-color); line-height: 1.6; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } [data-theme="dark"] .docs-page-title { color: #ffffff; text-shadow: none; } [data-theme="dark"] .docs-page-subtitle { color: #cccccc; text-shadow: none; } @media (max-width: 825px) { .docs-page-title { font-size: clamp(2.2rem, 7vw, 3rem); text-align: center; } .docs-page-subtitle { font-size: clamp(1rem, 2.3vw, 1.125rem); max-width: 680px; text-align: center; } } @media (max-width: 640px) { .docs-page-title { font-size: clamp(1.95rem, 8.2vw, 2.4rem); } .docs-page-subtitle { max-width: 42ch; } } /* Shared mobile docs sidebar toggle (Models + News) */ .docs-side-toggle { display: inline-flex; align-items: center; gap: 9px; border: 1px solid rgba(43, 35, 19, 0.11); background: #fff; color: #232323; border-radius: 10px; padding: 8px 12px; font-size: 0.87rem; font-weight: 600; width: fit-content; cursor: pointer; box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06); } .docs-side-toggle:hover { box-shadow: 0 3px 10px rgba(0, 0, 0, 0.06); transform: none; } .docs-side-toggle .hamburger-icon { display: inline-flex; flex-direction: column; justify-content: center; gap: 3px; width: 14px; } .docs-side-toggle .hamburger-icon span { display: block; width: 100%; height: 2px; border-radius: 999px; background: #282828; } @media (max-width: 720px) { .docs-side-toggle { padding: 8px 11px; } } /* Shared side navigation visual language */ .docs-side-nav { color: var(--docs-side-text); } .docs-side-nav .sidebar-card { border: none; border-bottom: 1px solid var(--docs-side-border); border-radius: 0; background: transparent; padding: 0 0 14px; margin-bottom: 14px; box-shadow: none; } .docs-side-nav .sidebar-card:last-child { border-bottom: none; margin-bottom: 0; } .docs-side-nav .control-label { display: block; margin: 0 0 8px; font-size: 0.74rem; font-weight: 650; letter-spacing: 0.05em; text-transform: uppercase; color: var(--docs-side-label); } .docs-side-nav .models-search-input, .docs-side-nav .sort-select { border: 1px solid var(--docs-side-border); border-radius: 8px; background: var(--docs-side-surface); box-shadow: none; } .docs-side-nav .models-search-input:focus, .docs-side-nav .sort-select:focus { border-color: rgba(107, 90, 42, 0.44); box-shadow: 0 0 0 3px rgba(189, 164, 92, 0.14); } .docs-side-nav .backend-filter-btn, .docs-side-nav .filter-btn { display: block; width: 100%; text-align: left; border: 1px solid transparent; border-radius: 6px; background: transparent; color: var(--docs-side-text); box-shadow: none; padding: 6px 8px; font-size: 0.8rem; font-weight: 500; transition: background 0.15s ease, color 0.15s ease; } .docs-side-nav .backend-filter-btn:hover, .docs-side-nav .filter-btn:hover { color: var(--docs-side-text-strong); background: var(--docs-side-surface-hover); border-color: transparent; box-shadow: none; transform: none; } .docs-side-nav .backend-filter-btn.active, .docs-side-nav .backend-filter-btn.active:hover, .docs-side-nav .filter-btn.active, .docs-side-nav .filter-btn.active:hover { background: var(--docs-side-active-bg); color: var(--docs-side-active-text); border-color: transparent; box-shadow: none; transform: none; } .docs-side-nav .label-filter-btn { border: 1px solid var(--docs-side-border); border-radius: 6px; background: transparent; color: #64593f; } .docs-side-nav .label-filter-btn:hover { border-color: rgba(79, 66, 30, 0.28); color: var(--docs-side-text-strong); background: var(--docs-side-surface-hover); } .docs-side-nav .label-filter-btn.active { border-color: transparent; background: var(--docs-side-active-bg); color: var(--docs-side-active-text); } lemonade-sdk-lemonade-d88f5d9/docs/assets/extra.css000066400000000000000000000056401515430344400223730ustar00rootroot00000000000000/* Note: I have not figured out all the color variables yet */ [data-md-color-scheme="lightmode"] { --md-primary-fg-color: #FFFBE9; /* Header, Selected Page Font */ --md-primary-bg-color: #000000; /* Header Font, Icon Color*/ --md-primary-bg-color--light: #000000; /* Search bar font color */ --md-accent-fg-color: #FFD744; /* Hover color of links */ --md-footer-fg-color: #E59800; /* Nav Footer Font Color */ --md-footer-fg-color--light: #3b3b3b; /* Footer Font Color */ --md-footer-fg-color--lighter: #3b3b3b; /* Made With... color */ --md-footer-bg-color: #FFFBE9; /* Nav Footer Background Color */ --md-footer-bg-color--dark: #FFFBE9; /* Footer Background Color */ --md-default-bg-color: #FFFBE9; /* Main background color */ --md-code-bg-color: #ffefb5; /* Code block background color */ --md-code-fg-color: #000000; /* Code block font color */ --md-default-fg-color--light: #E59800; /* Blockquote color */ } [data-md-color-scheme="slate"] { --md-primary-fg-color: #FFD500; /* Header, Selected Page Font */ --md-primary-bg-color: #000000; /* Header Font, Icon Color*/ --md-primary-bg-color--light: #000000; /* Search bar font color */ --md-accent-fg-color: #FFD500; /* Hover color of links */ --md-accent-fg-color--transparent: #E59800; --md-footer-fg-color: #E59800; /* Nav Footer Font Color */ --md-footer-fg-color--light: #929292; /* Footer Font Color */ --md-footer-fg-color--lighter: #929292; /* Made With... color */ --md-footer-bg-color: #000000; /* Nav Footer Background Color */ --md-footer-bg-color--dark: #000000; /* Footer Background Color */ --md-primary-bg-color--light: #000000; /* Search Font */ } [data-md-color-scheme="slate"] { --md-hue: 320; /* between 0 and 360 */ /* --md-saturation: 50; /* between 0 and 100 */ /* --md-lightness: 100; between 0 and 100 */ --md-footer-bg-color: #141413; /* Nav Footer Background Color */ --md-default-bg-color: #141413 !important; /* Dark background */ --md-primary-fg-color: #E59800 !important; /* Header, Selected Page Font */ --md-footer-bg-color--dark: #1f1503 !important; /* Footer Background Color */ } .hide-in-mkdocs { display: none; } /* docs/assets/extra.css */ .mkdocs-only { display: block; } /* Hide the page title in the navigation sidebar */ .md-nav__title { display: none !important; } /* Make page titles (h1) a darker grey in light mode, lighter in dark mode */ [data-md-color-scheme="lightmode"] h1, [data-md-color-scheme="lightmode"] .md-typeset h1 { color: #222 !important; } [data-md-color-scheme="slate"] h1, [data-md-color-scheme="slate"] .md-typeset h1 { color: #cfcfcf !important; } lemonade-sdk-lemonade-d88f5d9/docs/assets/favicon.ico000066400000000000000000003674311515430344400226700ustar00rootroot00000000000000 hf  00 %v@@ (B; (F} in(  ~ m>n8PphU@DF|upk_KBB@1tjqlJpojd^WSUẉYl WqYXajke`YMB?@`@O`Qz_f`ZXQD701ص: FCP`^TPQMA4--5BA@P^TIJPQH:009==ZMWIAJW[RB54H66NBK>GMKNMf%ȝ_4ԫLVŬK3<?>?A">$=)΢ 9۲P @tܺ( @ ~dPԜ&yngg?!ps}Ar{i_RHD!E֑jȀzvttodZK94U4+zsnkhfdca\VIAɀ6Xqww-vronlifc`]ZXXZx^rgk&entajkkkmonlheb_[VRNMRzXr¯ZlYna`acimnlifca]XRMGCBEJxuEKt]XX?Zbiljgca`^ZUNGA<9872/-,2ϥNx<NSIQ_dc^YTRRRQOJC<61.,+0;:GF GT`b_YSOMNOPNKE>72/-,0;;BBRFW``ZSMJJLNPPNIB;51.-2?=H:?GX^\UMGEGKOSTSNG@93/.4IBFgp=GW\XPGBBFLRVYXSMD=6108ICI:DTXSJB>@FOW\^]XPH?833=Q:B7>MRME=:?HS]ceb\SJA:48DCA.5{7CIF>87>JXcjlh`VLC;7=K45C18>=845=K[hpqmeZOD;;D1B890/34202:IZhqtph]QE>A`L>+2^++---/6CSbmqph^RFCH(Gamne_ ? -'*,-.1;HWdkkf]QHI_:R]vhKLNpQO=% ùR&(/0015>JW_c`WNLSPG>?!=;<@>ť0ɧ̧β/87559AJRVTONyQ P23 0+,1Š9ɦ?Ϋ?Ѯ5԰'װٴ 5?<98;@EIKNGSQ*, "~!%ś,ˢ8ЩBհGٵEܷ?޹94/ĄBLA=:;>CG@L GU'*$#$×x$ɞ,ѧ>ٲPXZXQŌE HSFCDKwj;fk,×)˟j.ԩ=ݴOYŤZLUWJ0ȝ +Ц.Ӫ'ʢ2ٲa0??(0` $U zSH{squ~Or~vj9tia[Y]zar^yϙGƏяҎՆtgb]SFBD4D!z͇|xvvvvqf^XNA66m3 bրytpnlkjiiie_XQE74̓=~vromkigfecba`_\Y|V|KC}ȾHu Iw}~|vqponljhfeca_]\ZYY[z^u_sdoOXvpgst spmnoonmjhfdb`^[YVTRSU|Zubofj1cmzYklkwiiknoonljhfdb_]ZVSPMLKLRwYq[lZmrcdBccglnonljhfdca^\XTPLIFDCDJ{OtWeRp^_]^bhlmmkigecba_]ZVRMIEB?=<=C~Hx5Gy]UXjX]dikkjheca`_^]ZWSOJEA=:8669?YaxD|UUSV^eijifda_^]\[ZXTPLGB=9642118>!=VKPrPX`ehgec_][ZYYXWUQMIC?:631/.-19Y6EPPMPZbefeb_[XWVVUUTROKFA<841/-,+.5ɐAQIK]JS]cedb^[WTSRSSSRPMID@;730/-,+,3жC=MPGJU^cdb_[WSQPOPPQPOLIE@;731/-,+,2?=HH,DLX_bb_[WSOMMMNOPPOMJFB=9520.-,,3@?ADdCNY_a`\XSOLJJKLNOPPOLIE@;741/-,-4CAJ@hACPZ_`]YTOKHGHJLNPQRQPMHC?:630.-.6LDFG ?CQZ^^ZUPKGEEFILORTUUSPLGB=951/.09KCB=CQY\[WRLGCBCEHLPTVXXVTPKE@;730/2=k6L@@$;BOXZYTOIC@?ADINSWZ[[ZWSMHB=84106B8@?>&9@MUWUQKE@==@EKQV[^__]ZUPJD>:622:JG>>7?57CKNMIC=98;@GPX_dhihd`ZSMFA<75:E5C@U53:6448?IS^flpqokf_WPIB=8;ENA89*1/489863126>HS^gnrsrnhaZRJC=:AO K=K3-.12210/04?GMBF47;.*,...--.28ALXbjprrojc\TLD@ETO;H2*(*,,,,-/4= ?<658<¡@ť@ȧ9ʩ*ͩΨϧϵ,9:875569>DJOSTSONOpUR7:l9r3,+.3š8Ȥ<ʨ?ͫ>ϭ:Ѯ0ԯ%կ׮س.>?=:878;>BFIJKLO?YT.0)"$)Û-Ɵ1ɣ7̧=ϫAҮBձA׳=ش7ڴ/ܶ(߷!"1BA>;989;>ADHKRP MX#%}= "Ś'ɟ-̤5Щ>ԮDײHڵIܸG޹Dߺ@<;9/3I C8Bn@=<<>ADYI'OJ[+0'4$"ƚ#ˠ*ѧ9֮F۵OߺTUUVWSKI\K۵QXJG H Rt8D.*(Ś%ˠ)ҧ6ڰHW`cb]ɱU5Jس@K3ę!-ˠm,ҧ1خ<޵GM侓MHFڵ OJ=r{<ě/̢,Ϧ+̤ ????(@ @[w|ȕJf~yy~~Pu ||E Tsic`bj~ۧqw k{cϑ 0=BAB`znhd_XPOUԩYy W|Mܐ쎔}ogc`\TJA?CIC-ь}zxwwxxwpg`\XPF<57̈́,>ㅭ~yusqonmmllmlg`[VPG<44ͫCy:J̃{vspnmkjihgffeeda]YUOD95<~;nzurpomljigfedcba``_^\~[|Z{W{M}D|Ex2Ey|{tqpoonlkihfedcb`_^\[ZZZ[{]w`t^t_q|^jjyzxtupnnooonmkjhfedba_]\ZXVUUUW|Zw_seoikXOkftjrZolkmnooonmkihfecb`_\ZXVTRPPPPSyYt`nck?bmdgnkl4jghjmoooonljigfdca`^\YVTQOMLJJJM|RvYp[l [mfgeccgjmnoonlkigfecba_]ZXUROMJHFEDDFMxRrVkTod6as__cgkmnnmljhgedcba`^\YVSPMJGDBA?>?BHzLuGKwPo]].[[_dhklmlkigfdcba`_^\ZXTQNJGDA?=;:9:>D|It GwZ]XWZ`ehkkkjhfdca``_^^\[XVROKHDA><986557SU[aehjjigeca`_^]]\[ZXVSPMIEB>;96532116:7420/-,+++.5ˠdr>PCJtGMV]addca^\YVSRQPQQQQQPONKHEA>:7420/.-,++-4кD>MOGFOX^bcca_\YUSQOONOOPPPPOMKIFB?;8521/.-,++-4C@HI9DGQY_bba_]YVRPNMLLMNNOPPONLJGD@=96420/--,,.5DBN>EuBISZ_aa`]ZVSPMLKJKLMNOPPPONLIFC?;8631/.-,,.6G CJPBAJT[_``^[WSPMKIHIJKLNOPQQQPNLIEB>:7520/.--/8ݵNDGG@ALU[^_^\XTPMJHGFGHJLNPQRSSRQNKHDA=96410..-0;ޚ&HCD2>BLU[^^]ZVRNJGEDEFGJLOQSTUUUSQNKGC?<8531/..3>u5P@AK:7420//6AJ?<?_;ALTY[[XUQLHDB@@BDGKNQTWYZZZXVSPLHC?;8531009G!E:>i9@JRWYYVRNIEA?>?ADHLPSWY[]]\[XURNIEA=964103=UK9=h8>HPUWVSPKGB?==>@DIMRVZ]_``_]ZWSOKFB>:74216Bl8Q9<\6;EMRTSQMHD@=;;=@EJOTY]`bccb`]YTPLGC?;8533;H,F;:879@517>CFGEB?;8667;AGOV]bgkmnmkhd_ZTOJEA=977@NKA6028=@A@>;86446:@HOW_ejnpqpnjfa\VQKFA=97;Eo8O68M1/37:<;:8532259@GOX_flprsrpmhc^XRMGB>:9AMJ<=3-/256664210148>FNW_flpstsqnjd_YSNHC>;>Gt*U>)6r.,.012210//026Ť@Ǧ?ɨ<˩4ͪ'ΪЪШϧϷ)6;:98766679=AEINQRSROMMPeWT6=;<91,+-0ž3ġ7Ǥ;ɦ>˩?ͪ>ά;Э6Ү-Ӯ$ծ֮׭ײ);?>=;98788:=@DGIKKKKMQ6eW243*%'),Ý/Ơ2ȣ6ʥ:ͨ>ϫ@ѭ@ү@԰=ձ8ֲ3ز,ٳ%۴ݵ߶&:A@?=:9889;=?ACEGJNWS Ne*,##'ě*Ǟ-ɡ1ˤ6ͧ;Ы?ҮB԰DֲDشCٵ@ڶ<۶8ݷ4޸/+)'-kD\BA?=;988:<>ADGKCQ =|& !W  ę#ȝ(ˡ.ͤ4Ш;ӬAְEسHڵJܸJ޹H޺F߻C@>>?;彘2۳ 8HE#CNAy@>=>?BDbG6L]UB%1 )H%!Ę ɝ#͢+ѧ6ԬAرHܵN޹QSSRQRTTNæEGX`PKN[zl)=/<)Ø$Ȝ"͡'Ҩ4خCݵOW]acff`VōL߻O9@23,ƛ(̡'ҧ.خ<޵JV]^\WĶQMDسJ޹?K7Ě&1ˠl-Х-ժ2ٯ:ܴ@޷D޸Dܶ>=ԯ L%u[C4ɠ/̣.̤+Ɲ~V???? ??( ^^^^!U}ꆎh7yes.}wsrty~ᶈz]uPpqpy{rlhecbbfoyzٳv:lt R€unjgdb_]ZXY`l{twPfxtI{rmjhfca^[WSQPU`}gx>a|sp$g~|zywvuutttttsttuutrnjea_][XUQNHC>9525;;9cǓ}{ywvtsrqqppoooonnooooomjea^\ZWTQMHC>95238^7B}{xwusrqpponmmllkkkjjjjkkkjhfb_\ZWUROJE?:5227|2B芲|ywusrqponmmlkjjiiihhggggggfffeca^[YWURNIC=8427ÛK}?4{xvtsrpponmlkkjiihhggfeeedddddcccb`_][~Y~X~VTPKD>858?|=~Rׅ{xutrqpponmllkjiihggffeeddccbbaaaa```_^]~\}Z|Y{Y{W|U}P~IB<<}Ay)?zi遹{wusrqpponnmllkjiihggffeedccbbaa``__^^^]]\\~\|[{[y[y\x[xXyRzK{FzHwcG{Il w}xusqqppoonnnmlkkjihhgffeddccbba``_^^]]\\\[[[[~[|\z\x^w_u`u`u]uXuUsVpVqz~yusqppooooonnmmlkjjihggfeeddcbba``_^]]\[[ZYYYYYYZ}[{\x^v`tbsdrerdpemgGii~}r{vspoooooooooonmmllkjihggffedccba``_^]\\[ZYXWWVVVVVW~X|Yy[w]t`rdqgojmliBemoiyzybwspnnnnnnoooooonnmmkkjiiggfeedcbba`__^]\[ZYXWVUUTTSSSTU}VzXwZu]rapfnikkh/ilm`uvvJtqnllllmnnooooooonmmlkjihhgfeedcbba`_^]\[ZYXWVUTSRQQQQQQR~S{TyVvZs^pcngjhghh~qs.qnljjjklmnoooppooonmllkjihgffeddcba``_^]\ZYXWVUTSRQPOONNNOOO}QzSwVt[q`nckehdiopolihhhjklmnooppooonnmlkjiihgffedccba`__]\[ZYWVUTSQPONNMLLKKLLM~N|PxSuXr]n`kydgaimoljgfffhiklmnoopooonnmlkkjihggfeedcbba`_^]\[ZXWUTSQPONMLKJJIHHHIIK}MzPwUsYo[lJZp\hobiMgedcdfhjklmnooooonnmllkjihggfeedccbaa`_^][ZYWVUSRPONMKJIHGGFEEEFFHJ{NxRtVoWmWmgh ecabbdehjklnnoooonnmlkkjihggfeedccbba`_^]\[ZXVUTRQONLKJIGFFEDCCBBBCEG}KyPtRpVmToegda__`bdfhjkmmnnnnnmmlkjjihgffeddccbba``_^]\ZYWVTSQPNMKJHGFEDCBAA@@@@ABE~IzMtQq>Osafj_bG`^]^`bdfhjklmnnnmmllkjihgffeedccbbba``_^]\[ZXWUSRPOMKJHGEDCBA@??>>==>>@CGzKuOp Mraa_\[\^`begijllmmmmllkjiihgfeedccbbaaa``_^]\[ZYWVTRQONLJHGEDCA@?>>=<;;;;;<>BFyIuLHwNo`"]l[YY[^`cegijkllmllkkjihgfeddcbbbaa```__^]\[ZYXVUSRPNLKIGEDCA@?>=<;:998889:=AEyJt Hv\]%ZWWY[^acfhijkkllkkjiihgfedcbbaa```___^^]\\[YXWUTRQOMKIGFDCA@>=<;:9887676679=;:9877654444468=B~KxE{YY1VSTVY\_bdfhijjjjjihgfedcbba``__^^^]]]]\\[ZZYWVUSRPNMJIGECA@>=;:987654332212359>C}&A~Y\VSRSVZ]`befhiijjiihgfedcba`__^]]]]\\\\[[[ZYXWVUTRPOMKIGECB@>=;:8865443211000125:@h;EwVW6SPQSW[^aceghiiiihggeedba`_^^]\\\[[[[[[ZZYYXWVUTRQONLJHFDB@?=;:8765432110/////027<:9765432100/...-..049@*>ST2QNOQUY]_bdegghhggfedcb`_^]\[[ZYYYYXXXXXXWWVVUTSRPOMKJHFDB@><;9865432100/..--,,-.16<`:TVaRNMORVZ]`bdefgggffedca`_^\[ZZYXXXWWWWWWWWVVUUTSRQONLKIFDCA?=;:875432100/..--,,+,-/3:VCRS'OLLOSW[^acdfffffeedba`_]\[ZYXWWVVVVVVVVVVUUTTSRQPNMKJHFDB@><:976543110/..-,,,+++,.28@>W,PuLJMPTX\_acdefffedcba`^]\[YXWVVUUUUTUUTTTTTTSSRQPONLJIGECA@><:87643210//.--,,+++++-07>(<QSMIJMQUY]`bceeeeedcba`^]\ZYXWVUTTTSSSSTTTTSSSSRQPONMKJHFDBA?=;986542210//.--,,+++++,/5=I;TMOWJHJNRVZ^`bcdeeedcba`^]\ZYWVUTSSRRRRRRRSSRRRRRQQPONLKIGFDB@>=;986542100/..--,,+++++,.3;h9RV LGGKOSX[^`bdddddcba`^][ZYWVUTSRRQQQQQQQRQRRQQQPPONMLKIHFDBA>=;986542100/..--,,+++++,.3:ŀ6?NO2IEGKPTY\_abcdddcba`_]\ZYWVTSRQQPPPPPPPQQQQQQQPPPONMLKIGFDBA?=;986542110/..--,,+++++,.2:ȓnxDU7LwFEHLQUZ]_abcdccba`_]\ZYWUTSRQPPOOOOOOPPPPPPPPPPOONMLKIHFECA?><:87543210//.---,,++++,.2:ˠODNQIDDHMRVZ]`abcccba`_^\ZYWUTSRPOONNNNNNNOOOOPPPPPPOONMLKJHGECA@><:976432100/..--,,,+++,.2:ͩK CJL:FBDINSW[^`abccbb`_^\[YWUTRQPONNMMMMMMMNNOOOOPPPOOONMMKJIGFDBA?=;:87543210//.---,,,,,,.2;ϭK DQ>JxDBEJOTX[^`abbbaa`^][ZXVTRQPONMLLLLLLLMMNNNOOOPPPOOONMLKJHGECA@><:976532110//.---,,,,-.2<ѫM ELP GABFKPUY\^`abbaa`_]\ZXVTSQPNMLLKKKKKKLLMMNNOOOPPPPOOONMLKIHFECA?=<:876432100/.---,,,,-.3<ӣQGJK)D@BGLQUY\_`aaaa`_^\ZXVUSQPNMLKKJJJJJKKLLMNNOOPPPPPPPOONMLJIGFDB@?=;:87543210//.---,,--/4>Ә^HGIYB?BGMRVZ\^``a``_^\[YWUSQPNMLKJIIIIIJJKKLMMNOOPPPQQQQPPONMLJIGEDB@><;986542210/..------/5>Ԉ LLF@?CHNRVZ]^_```_^][YWUTRPNMKJIIHHHHHIJJKLLMNOPPQQQRQQQQPONMLJIGECA?><:976532100/..----.07As??JN D>?CINSVZ]^_``_^]\ZXVTRPOMKJIHGGGGGGHIJKKLNNOPQQRRRSRRRQPPNMLJHFDCA?=;:87543210//..---.19C[AGIB=?CIOSWZ]^___^]\[YWUSQOMKJIGFFFFFFGHHIJKMNOPPQRSSSTSSSRRQPNMKJHFDB@?=;986532100/...--/2;F=CFH;@<:87643210//..../3=H EDF\><:97643210//../18BݕmNLB<;?EJOSVY[\\\\[ZXVSQOMKIGEDCBBBBBCDEFHIKLNOQRSTUVWXXXXXXWVUTRQOMKIGECA?=;986542100///02:FhCHKUA;:?DJOSVYZ[\[[ZXWURPNLJHFDCBAAAABBCEFHJKMNPQSTUVWXYYZZZYXWVUSRPNLJHFDB@><:976432100//04>I8FGK @::>DINRUXZ[[ZZYWUSQOMJHFDCA@@@@@ABCEFHJLNOQSTUWXYZZ[[[[ZZYWVTSQOMKIGECA?=;9865321000016BMJFI ?99>CHMQUWYZZZYXVTRPNKIGECA@????@@BCEFHJLNPRTUWXYZ[\\\\\[[ZYWVTRPOLJHECA@><:87543210003:EnPEI>88=BHMPSVXYYYXVUSQOLJHECA@?>>>>?@ACDFIKMOQSUWXZ[\]]^^]]\\[ZXWUSQOMKIFDB@><:97643211014=HWE%zDH>88===>>@ACEGILNPRTVXZ[]^^____^^]\[YXVTRPNLIGECA?=;98654221126BM"JEI =77:@EJNQTUVVVUTRPNLJGEBA?>=<<<=>?ACEHJMOQSVXZ\]^_`aaa``_^]\ZXVURPOLJGECA?=;:875432124:E[PDJ =769>CHMPRTUUTTRPOMJHFCA?><<;;<<>?ACFHKNPRUWZ\]_`abbccbaa`^][YWUSQOMKHFDB@><:876432226>IZE[ES=658=BFJNPRSSSRQOMKIFDB@>=;:::;<>?ACFILOQTVY[]_abccddddcba`^\ZXVTQOMKIFDB@><:976543248CNKD1=647;@DILNPQRQPOMLJGECA?=;:99::;=?ADFJMPRUX[]_abdeefffeedba_^[YWTRPNKIGDBA?=;98654345k7359>BFILNOPOOMLJHFCA?=;:9999:;=?ADGJNPSWZ\_abdfgghhhgfedb`_]ZXUSQNLJGECA?=;98754458AKEI=?J7237;@CGILMMMMLJHFDB@><:988889;=@BEHKOQUX[^`bdfhiijjjihgedb`^\YVTQOMJGECA?=;:875446;FRN>@'82259=ADGIJKKJIHFDB@><:9877789;=?BEHLOSVY\_bdfhijkllkkjhgeca_]ZWUROMKHECA?=;:876558@Jh>U@C92036:>ADFGHHHGFDBA?=;98766679:=?BEIMPSW[^`cehjklmmmmlkjhfdb`^[XUSPNKHFCA?=;:87656:EP!MEg;30148;>ACDEEEDDBA?=;987655568:<:87668?I|S};=[500258;>@ABBBBA@?=;:8765445679<:8777;DN1L=?%70/0258;=??@@??>=;:87644334579;?BFJNQUZ]adfilnopqqqqponljhec`^[XURPMJHEB@?<:8879?I]Q?E9ߦ1./0368:;====<;:9875433223468;>AEJNQVZ^adgjmnpqrrrrqpomkifda_\YVSPNKHECA>=:988AEIMQVZ^aehkmoqrssssrqpnljgeb_]ZWSQNLIFCA?=;99:@IXQ=?7/--/0246778887665332111123579=@DHMQUY]aehknpqrstttsrpomkhec`][WTQOLIFDA?=;::=EO=MA\:ۈ2-,-.013455555433211000012469<@CGLPTY]adgkmpqrttttsrqonkifda^[XUROLIGDA@=;;<=>DM`S:<>4.+*++,-.///////....-../01369<@CHLPTY]adgkmoqrssssrqpomjgeb_\YVSPMKGEB@?>AIR%P>D8؛1+***+,,--......------./1257:>BEJNSW[_bfiknpqrrsrrqpnmjgeb_]ZVSPMKHEB@?@FNeU:<74-*)))*+,,--------,---./01369<@CHLPTX]`cgjlnppqqqqponljgeb`]ZVSPNKHDBAAEMUR=F9ֆ1+)(()**++,,-,-,,,,---./0247:>AEINRVZ^adgjlnopppponmkjgeb_]ZVSQNKHDBBDJR8Nj;="6.)''(()*++,,,,,,,,,--./01368;?BGKOSW[^adgjkmnooonmlkigdb_]ZVSPMJGDCDIOqbS>/:U3*'&'(()*++,,,,,,,----./0247:=@DHLPTW[^bdgiklmmmmlkjhfda_\YVSPMJGEDHNUS?F8׊/(%&''()*+,,--------../01358;>AEIMPTX[^adfhijklkkjhgec`^[XUROLIGFGMS*Q>>>>;>5ڲ+%$%'((*+,-------....//12469;?BFIMQTX[^`cefhiiiihgfdb`]ZXTQNLIGHMSKGVy{o j!kFjgi}hgdbwb\b8cwpI6<,2(#$&'()+,-.../....///0023579<@CFJMQTW[]`bceffggfedb`^\YVSPMJIHLRj^Vbe_&]l\\ZYWTQLHB?>=?UCoX?&8>/%#%&')+-..///000//000113468:=@CGJMQTWZ\^`bcddddcb`_]ZWTQOLJJLQZVQMuS)TSRQQPPOMJE>7/'##$&*0136F,##%'(*-/0001111000011224579;>ADGJNQTVY[]_`aabaa`^]ZXURPMKJMQWUIH LhMMLLKKKKKKJHD>7.¢%âĢġàŸ<4D)#$&(*.02222222222222233568:ä9Ť1ƥ(Ǥ ȤȤȣǢšĠŸC 35'"%')-134444433333333345679;=?BEHKMPRUWXZ[\\\[ZYWUSQNLKNRVT;wJ?,CED@<:::;<=?@BB£CäBĥAť>Ʀ:ǧ3ɧ+ʧ"˧˦˦˥ʤȣǢŠK(4%"%(+1577766655544444456789;>@BEHJMOQSUVXXYXXWVTRPNLLNR~Y V9zC=0AC?:64345679;=¡>ã?Ĥ@ťAƦ@ǧ?Ȩ=ɨ:ʨ4˩.̩&ͩΩΩϨΧΦ̥ʣȭj ْ"&).59::99887666555556789:<>@CEGJLNPRSTTUUTSRPOMLMOSaZV/n?9*?@;6200002356 8á:Ģ<Ť=ƥ>Ǧ?ȧ?ɨ?ʩ>˩<̪9ͪ5Ϋ0ϫ*Ь#ѫѫҫҪҩѩЧϨ%*29<<<<;:9987766666788:;=?ACEGIKMNOPQQQPPNMLLMPT=eX97=>81.-,-./01ž3ß5ġ7Ţ8Ƥ:ǥ<Ȧ=ɨ>ʩ?˩?̪>ͫ>Ϋ<Ϭ9Ϭ5Э1ѭ,ҭ'Ӯ"ԭխխ֭֬֬ԫӲ",6<>>>>==;:98877777889:;=?@BDFHIKLMMMMMLKKLNQV=]63~ ;;4.+))*+,-/Þ0ğ2Š4Ƣ6ǣ8ȥ9ɦ;ʧ=˩>̪?ͫ?ά?Ϭ?Э>ѭ<Ѯ9Ү6ӯ3ԯ/ԯ+հ'ְ"װذٰٱڱڰٱپ*8?@@???>=<;:988778899:<=?@BCDFGHIIIIIIJLOREXU5}J߮8a91*'''()*+œ-Ý.ğ/Ơ1ǡ3ȣ5ɤ7ʦ9˧;̩=ͪ>Ϋ?Ϭ@Э@Ѯ@ү?ӯ>Ӱ<԰:հ7ձ5ֱ2ױ.ײ*ز&ٲ#ڳ۴ܴݵ޵޵޸$6?@@@@??>=;:98888889:;<=>?ABCDDEFFFHJMQ`U:]3360'##$&'()Û+Ĝ,Ş.Ɵ/ǡ0Ȣ2ɤ5˥7̧9ͨ;Ϊ=ϫ?Ь@ѮAҮAӯA԰AԱ@ղ?ֲ=ײ<ײ9س7س4ٳ1ڴ.۴+ܵ(ݶ%޷"߷ 0@BAA@@?>=<;98888889::;<=>?@ABCDFILOaTcX++"(" "$&˜'Ú)Ĝ*ŝ+ǟ-Ƞ/ɢ0ʣ2ˤ4̦7ͨ9ϩ<Ы>Ѭ@ҮAӯB԰CձCֲC׳C׳Bش@ش?ٵ=ٴ;ڵ9۵7۵4ܶ2ݷ/޷-߸*(&$$##$>DECBA@@?>=<:98888889::;<=>@ACFIKOHRnZ!!|Z!#Ø%Ě'Ŝ(Ǟ*ȟ,ɡ.ʢ0ˣ1̥4ͧ7Ϩ9Ъ<Ѭ?ӮAԯBհDֱD׳EشEٴEٵEڶC۶B۷A۶@ܷ>ܷ<ݸ:޸8߹6420..--,,g.MGFNDCBA?>=<:9988889:;<>?BDGILXOVS-4)&g"—!ę#ƛ%ǝ'ɟ)ʠ,ˢ.̤1Φ3ϧ7Щ:ѫ=ӭ@ԯBְDױE׳GٴHڵHڶH۷HܸHݸGݹEݹD޹B޹Aߺ?><:9888997Ṡ7ر6ܴLNHF=DpCBA@?==<<<==>@CDFIlKX3+X&!×ř Ǜ"Ȟ%ʠ(ˡ+ͣ.Υ1Ч5ѩ9ӫ=ԭ@ְCױEسGڴI۶JܷKݸL޹L޺L߻K߻JIHFEDCBBBDFFB佼?ܶ!@߸`LGF$E?EXDlCzCBCD{EmGYI?J#MUAJ690J+%–řǛɞ!ˠ%̢(Τ-Ц1ҩ7ԫ;ծ@װCٲFڴI۶KݷM޹OߺOPPPONNMLLKLMPSQJEݸ.FFЫl+%; 3=.("ĘǛɝˠ!͢%ϥ+ѧ0Ӫ7խ=ذCڲGܵKݷN߹PRSUUVUUUUUVWY\]XOHݹ2PEԱ:>621+—%Śɝ˟΢"Х'Ҩ/ի6׮>ڱDܴJ޸NRUXZ[]]^_`bdfgd[PIܷ(VEѬ=C6)2-Ø'Ǜ!˟΢Х$ӧ+ի4خ<۲D޶KQV[^acfgikmmjbWN⽅H׵KC?I8#4u0Ś*ɝ$͡!Ф"ӧ'ի/خ8۲@߶IPW\adgijhe_VO⾶IݸHD԰Gٴ?L:6˜i2Ɯ-ʠ(ϣ$ӧ%ժ)ح0۱8޵@HNSWYXVRNJߺGڶYBӭn:ɣCM=8ÙO5Ǟ1̡-Х*Ө*֫,ٮ0ܲ5޴9=@BB߸BݶB۴Aײ@>Э!zf:á=>;Ś#8ȟW5ˢ1Τ/Ѧ.Ҩ/Ӫ1Ԭ3լ6խ8ԭi:Ҭ9;Ъ=Ü<ȡIT; 5Ǟ2ɠ2ʢ3ɣ7Ơ =;Ù?????????|??PNG  IHDR\rfirIDATxy%IU&ZnVDDlV7AtDW:n(˰ (R=, "3,BwW͌#32O8W婺/3#"#ΉL`I&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&9ӄJ~ҵk w>ܖ|s+O9t-1} p 🂣~費_yxcINFߺK tUs&0;?Lj.Gv$3+]}7`UQ7Y .@p8r "nu8`B4 dhM7~etE>$M>=TUZ흫|ӂw趑)vSiMẖXlas6/ވ_=3VVjqgpѢ uwZK[$OFd?rzz:KB~TRHw]!Bc槶#r?~#g:nvh~үoƹ>>-ZߥA>`AS[ڟV@G) FOvm&?~|kK-o03F< OrUcl5A4}݁ < DK<=N:Ꞃ6Zz>(ucz% %3@z Ǐo67~Ϸ{L&˞+߾^@F j6QGp>Uh+߃]Xl1G/U B!dleslnׅ~7LrÖ=SzEpO:_n<@t?zCSHIi!zOBF2]F.Aw>M۞Whv+}/^~Cozl<յ-fk[{^b@_{sH,Đ&hB$AWk xsOsdiQGu{<~Kn뻗^} = >mJ%a(7(=#H܂NAАGPgK*yɗ^-A芣;ޜz-Y|/4%T9zჇj'ߺsۭo q`Ĩ~?Fb)dN܉H~%"O;``~]{0L;fPck=[}Y%wh͠p z7>L5@LԾ\u@S?9<i%D<{Ӝ߼%95ݸ|>K(A\J<2(ۭ['u$I@ +@d ]De00^$``(7k6gw~d^rMpW!ܓ<Do]8]?͚ݮ+VEԷ$ܽ3>r@s1#TIˮ*#G~Q5s:9^ @UOXt_d_< 1Y$#RХ%_0JalmwiG^5\ ۽糽 p#eܼr [GsަНLTm%ЊnY7tZG[6Á7nIl9ip[Wi߰Yx鈾rKZ{=i\ 3}H݈)>hh+/rW`(-m{B`,tl~y8~i] "i! ZrB i{O7,sOl{$R?}9PgfU8+cmPiv{*ä5('#y3E/GT?Q 6[%Ad(k M`̷t1,U9<{~ 7jA_,|w-$M.0hJGG@~AT!4j^8+| qo+p}9i$t-0H,uD1y+`)dQܞ$ ^<(:4}xh!,÷~*wY=wѭp5kKŘ, ZwqbzT5B !84YTW_ 继RG^v>7[wۉJa ?97g _?AZw0dRī. z0GKetni_0.nt윯4PMgybT:^} I'zzJ@ޏ"б.MS-3h&Tzï[|kw`ct9ppij#OL_Yz~Wtap%)mV_ƒ=Ke&a`Q9H klkE J߾e`ŇM% mՐIiGX% R`nICh<l̞{hb?d~V?;Ckk7&蔀@O4mGY Bץ>eVIZk$lB\sTt*nATh7`/+[t>֟,R,z shZE/.qх/d ]NZ/ꮏY?̡esuTԔR%VLcr^`dv_!"dIhZS. Ⱥı!&|<:;O؃:'q#EnP27V_ @ 1ur@ZeǼ}nQ/޺to+ >tg @Rv@{RB8a;Gp%pnob΄[ncf:5٭6C]Spqρ9@hoz]AGT_햻2}dh/~3p0o\;ޭuf-.k *?^q(A=uJYX%DYlr 1 _B-o[P/5g_x=Ujx0ȑo A;_ )ՐQtt]O@.[&wV?pC}mΩo=3TÓwUoonH?ȳtTH$xNsyy3R0@ Al{O8_>#4 DY 1 TE lPszeb"[Qc0-,5zisM6sgVyT.w3y[UgKǷK%0B3(+L;$?` 4<ze7QOd9e >O8TC ]`0N["J `X>I.*FX_-ja 7s_Kg[_sfGwڏb'Ynt-%䋍h>H$2F!NՍ?nG@% p0Wܞ_zΝ_S3QN_|w؁5\WHSoCwnA BOTƘˀ_1L] 3Z ȯ]ppp{ʇhy[[9Tk8*飠GF_6K@Kq GRA(Dv/ϛчڷ*p9 ?]?P|El_}ip;A2`P1ljOv[` ,/(?38qom؋?.mUyT>R~:8@-PN^##a耟 H:i0X/jŶu׼TcLӢ/7ѫ9[e9u7 D&.AbYzHHkA@Tw]zH>fůƽ1kȻ*Y}w;Xg%6ec>Y^ۘUh);_W'SHp1Ztǘ],X+@'<饵Hנv?`jP},@*@ߍ@10^kq-}^iW uz8=gsE ݁4m%1ׂŏ#H>,>pC`n|]荍9 '2,?Dߥ`*_>"CX哱8FpvQ! qlSh<0 \DHw@Ch[Zj7((G^o\WwUuwvلP$Fb$t?>K sX?_}W衳}Vg- $V_0FWmBҘl{2ȮCqыP=I~L @0 tJ`kڻ]yS{9u_ g|մ/th?긏g11}#I=Տ~>p4?4;yt_κ׷1T oHFx:*´/(+ؗvXkﰔ# __6R @3  @ ڛvp}3AZŭ.| -s]q"է8'Hrȑ߿񾿼q8*_NZuH$W֝Y~=5w̒kd +H{m H Э7AnsWDϙ"'` 3g-ڡ6oj}?tl[&c/=ppf*~ An\.KV2)򗀯ն9>`?{#.R_(9H(h` 4P{l6ٞo_f:rW[{w8t|c}}BK,>?IPgJ#zdЕ,EP Ym1 PJ, .u2%$@17k>{.;*|M_G766fU|ԁ_Z}ĦK+YY_;a.^H6<ۤLNbU!ޢ1עa6}j~]vbaeR+ѣ:4x`cլAUu~?V7tE Q^0W+ }E.aP^bEݱ2*^m+7w!L `Ef5fUu& A?>Q_|:G*NS27*d]Cr=Fi Ojjc_yeS:#dR+{ammj綻Ocx3Qk0ƕȘ"謸,\R |I^rmf %E`N%f◤}5:~㺎dl@~{Zw<p=I,սj|6?ŅN&H-1Zt?2,φ~Jaճ2گ|{ɧÒmq-34U@Zq#Cҗ#kko~?+˒<+HV V4[H& B$J Jm05fn~/Ϟ .ZUڬFC]t]+M _ʕ`P#¶"飱iIAh[1@l/x,_vZp jȻpV&P#GyZUw(L6بFodZ,G h+ȫ%\, eJ1 Q@3p ֪Ŭ>qXiL ~`߻f%z=ߤE,l[Uۊd]Xa\oF +Ӭ{YRh|p#Pk~~s6<2)C}h׿2uw /Az>stgצ ,r f3/A`ڛ/ Ҁ.Rrol#>3:G *wo.0f7?}Zܲme?e<W%pPz]V@b,f]! ƍ5Ye5y(3,WĚbPZ{u{F?o0/^G/;}ڧS&GxӵjŁ>$/gJ`Ҏh.A%13%_6}>{{>FPm>U&?+B+}/|mx`ƱZsZFw=ݥeoב(֥("W*xK`TEG `غN}"_ާlIҼ{.oeSW`V׫/=gL @'WUdl2V @&*/Rd[Ӣ Ar*mn8R7)ӡ7:k]tFn=P5>]G9#>nyIjkrE&R4]sD}K@<4/X Ȭ36Ҳ}Wan-ZgGw{;/P}ki@'s󝯟0PN|8Źe#髈U^Sft] Ծ b?h#'Sl4S1,zirA@}'=s\w *7w~V69r;CkU}PNt!eW~◘@ Y=0"OoYseV*YtR$1Oʘ"KOjG!% }7tĨaãqɤ||v`#x@n 2+Jl2BҘR-h%ĹikT9]IaegCypeG XAT~Zg+^iIf~qh#tkzX iu`I-[w eM6 k'#̉2!_fWD-Drxp?g{G/?:fﯨ߀3'0b)gHڧg JH<=r\ў`Ȫ E>H%X~/zJe+҃oV`N] w]{XoQX7܇$]mtSsI/9ŗq[,wa'vxd @G5~aLn~@n [ ,ܤڟG ^闛@Y7CTPC5%x}s[<e"p +O}st[y< N~T_Kw`wޯCG9e?Z > 2/z [[Y|7>*ߧ]0)Y< kMN x`̶q"mve_+ pk/g S~ ^t-J+z}Xj J(Yjw޳g.0]dn@ \x_-}U ,r'~M{?Dx*fPyפ~)YOt_H_ްXzk(3@M`ۺ>s}…<ɘFu \`UXUûȿH h@weFYmM3-ee^SWdhb >:Иmi'*A-wA쳲DEбbxj] 7Im-VpY%FdomE~)¯z P , J#e P{d><F(@,a^{(R0#k2?Yf ˾ߋ3N/uTg:@ DF[%qM%!QJY-㱗I#+)^^٩}>35i<?i'Kh+4܂b/д]:aQQipbf/1-2VX˕Gu~ 'ޚONX撊' lWYAX7"x)(_P}.s ҊvY:rPXܬxC'Qءh\5Xj%/Y}Bգk"\ [?ҥ(0"lG(~eig V߷mOyX.>|#~WoBz܁xf*үػw:-I@%dTX)ݗy(Y[_fu⢲uӓJ e/ i0['ɾRׯ_)p}&R Eo"ꗆȗu@Г,j:4)Z?Qi$)8Եk%}ޢX@'QYQ˒$DPvp1zO@\YRo WJ>/ >e55hqF"cibkQ t BG?@7ڬ}*.H>'HW!y[(}L>ޢ ӺNugE<CYꂃ1\ OOPp`[-:&POPdo֑iKۥbŽZ;й8w?cNA!nN?S?s/P|ؽHJ=OO9'$"שմWmXqF!kݢ2ܳJ̀瘴7 v*W?{qe0]u֟,Pjihy2X" Il4k]Bs)=GRy +ގ宁̤LJu pwsoC8Ųo_>$\oIKD!IWqJnI5RഽY Z?*S3V-/d7ĪS_gYB$yk4-# 0Q9#쓓]xӯ@Y%̡ocOr }%xd6Aї)Eגp2sesiV @v ?XUtӶ>l?dw?Ӂ@T8EEk1 9+@Q&q|]F|%-Y@dhg zW_[ʾQ$c'C?9 [P.L5񓒀{^f/1 %/ @ԭעQz^ .S/*ʌnPx.?ӄpM%b;kD JXFdb쬮U(2%ަ#fXt]^=+mvdWx',HC, AG}}`NJڼ>h ,9XM+ 1Km;r LR1{aUĸ~}8+؃Y<&+8Eo J%0V,YU,E5ִ]o*e_URCd2>:("awM@hP7ɾQD޹&-}+WՖ+3TL 2/֑g:N;@[`SQ0v(EדRcB`xe8Eo3 EǾ؛h%.?0 2(/A_10A9h/8* +E"rԾƽ4+P)b!'iq_o)}"A wR0`Mő bȾ+^֭٘d 6_6'_*ץR ]ga8@ D57 Bwm~$nF2.Y F_ xC+_[b,@g)k2<ԹpE`J%)[8$%bT`@ <ꇬTe(o!@1atoՀҭX>0LtBJI^Ж :c(\K0dֺL`jd?:zye? iG @"M뺤w+P'q,.Yld4)d^KhYJIקPO h1[X[ñ$Ϳb\R$2KbXd T:ҲrR" VN˘y%&`U 뜝 *#ʡtPT&B=@"ܺ?!$}w#)4,Х^r ;<Bz/Z+nVz]Ri׻ aĹsPyd**@[B1?=S Vj]bexm ŗ{fhsGoP&]˗PX^u8VKҬ}񭏆犲2Y{`Rxy_On nBZ+Tr#H!v O 7 WCZp/gN53v 8S:-,ۙT4=ZMpz}nZm-ʳUv2PpB%~>]}/0;1@II=đ"pK`Ibx^[[PSe Vnfgyl짎5ꛋ}=  z{bu/Do3E5hiCYٍK 4kVl`ח\OzGJ' +>?z8=y{Hd4ƅ1+nQ#3R(6z!iwpx7 oMqmqlxO cc}1}7~(,Ѱ0I7R.-+ \JF h e'9fO|mѳUe ˂PW O=%7 .~gL΁I%bZS]_pl&*`|r.K4{'- Jykj ;6##JJL pTSM=Ko@wg+(eT " l LZ>]:0Ӫ>qP!_Qe Xؠ"GMXY{{ZPB@@k W > .$!uP,tɺ-a+t j:.1Z݀%&*6DY>} nԗ)=ӓʪ bd#74CKO%+e=0@g׮=(ZpX\$V~1Y\f˺P%>zP%`( gwuPc !wܭA?p-P C>]QJVi\*W:WE"i%LxS}fc/iPtݠ%~i˥F{Uo+.9vA&0joN鍕x`PB }yηfOD'YIYR!Cv}Uֽ݉hK\6q<pPa5(|\꠴\-xv>}ν}21V$X0Ƣ+bdpg]o1lJV_3xoIYMdž+[Uh5d64 p/X.W}1)%k.Ylx$g@[i<`Y} :T-;.*~kf RET_^R`if ı,Ͳ+^/w8tK }O4o `y<` `%ؐ~/1tUohfE[B)Lv;f @Wԍ"-ޫ`}R+l}۴F>f.\!N1%X zL$nm6WnHf`e6:Yy]#/ȏ^f: a!|\}sq u6AwblR:@<9"p줘:Vi[}k, *?z^E@>dQ|Ɗ-1w+p߹[!rJ~B@S!oAK$)[:,@>7 ]F :s bEK.0: ()U:^ɏ:EZc?|ťpp PJIe+iP/wXR.KDn}Keӄs06m,0`H?.uu["#F>6L(;8~3Oz/jzNЕexYmdڂb@9^qF ƾb(-?pF} Wo\5=Ծ+X-%n]BߐT\c՗XRذWVu ')Gd&̋UYkqx$z9kua`oB8#) ^C}4 ղ1Yk2c]fneK(UC#,$&g\Ǫ@u󒺌܁UY@Sv׹ g8Iٿ {w/r!}ɦ(i`(d0me]|ye2˘1`~rȴnW&{n.[Xy'm-;N'm v1?*OY%Wȗ׏LwKN+䅎Kթo\*>Q_8i `;^# VēAJ,U5xgRBٕ|Vyb3}G;0n >}Hm) lq%p"4lg'\FB3} 5à Lj|)[+t1]+tZ4=BEaA2Zn*2˔Bf݋J6&NR@8d]ohTC-)T(=.IKX(~U/?NY(Vd#ƲsDqa `wIʾW0;]*@EUo?ZV^Xr~uuaYNU1 (36Cԥi}޷~RLU 58I s$_ X@lTI Jb)u @e\>*+2CN4#cPV=o> UaHYΛ$eR Ͼa4NQX,yGֺyˀԣ&+R84t^2߯^)ta(w2]|uu"8q蒊 >M yPz9'(Uzl]0L( }=Ku+r>zH0Fĸ]ʤ:i .@c5Gy) IYK%XD=$}O:בuώ%': g%WK<}%/7>q-YT?i躿z8I@'/zcH<\^!m)X.AU+7q=:WzL%䀖X "*։?G֕TЯ2-EC?Mpj7c$aKt7$QJ`2L}K..A r t@E D(ʺ{N2K#I0H/}CB4-X[͍w-L `%WGS= 쐹zr  AĂ2+--?},JLTgu PO*l,EЭy;iSREEz B1_:'UMh/;r|7V/lW/n<:!!ZYXV]g1 v4E\e~} @ o}2cwf >'tEP/f/?zR;˹>j*J)EoSpdbїvݗT?SLe @w#jg@@??CSx%CQ$@(৾K\&DOma6=)EpZ7 *#՗n=YF(pFȭ4/^K ?Y|@%#@+6!ݵ>oˈHbq5pg.׍Gt8+_ϡtpa@W d,ACCw& hePy,rV`8#U V ȕ>n<(o_W<) SA"&J'JS/c 1& !cȕ4R@g9}EuJ eM/\4x?ʩjϓ8_P]1_|Q(Tv|I# vK#"ew@+o h`iRbK@/u7mgL[6_ F|Q5w*4űin%D8ж2\\]zRAGXy|t&ONTV2(9J22RаQ:Y']1ջ6xX0S`0a0WJ/E/@O.|P&pBgA~G z,w u˷XҺjhTPQ%DK~d? Y|=V`hshܯv<)/,_G? GY)YPE2^R YЊvRNeqK(Jeҙ|= n@xo?J FA>/[%PP{u:mxR'!?~,Fm%^Rr;޲ XRnI[2;W;D .?s_#:kAF}oCg-sta:*& 7R<8cHԯˈ}7Y٬˭XL2I7X9%lGq,sKt/ҸKk6=C:Si< TZ//qIAFԳ #}}Gh*4uՎkxv_ȤvM{[%G ʁB5 [Z<򷁔)$>tWvLdպoev:oz,k%Q{>Pxlqw:[vIwT'4~_6B^kIR }_eÃ9(+nu?fzvM@(nTrRk.GP%/F${4EǼt:vQ軞3BƯ)+0-%.) mim]%-aل܆>D>)GJE?eδ#|]2%C&T=[_z> ]#.tۻU.CPl+:[JD$ˀX/2/1E1DtC-zF_E>0 cj"|_N )ÇÃq C @G@`YQ[Q>((BZ_n{5..;NA H)x#=axlG:W FH׳! 4?~|JiP=]aOme|S־/)Xo0'VeqЏâ/m-.]OU;"!ٹACw DmѮ~I,fZU`3Lq_!_@_$׻:ju~_Gc t֞9G*Je 3=Ϲzeޞ&E  m q `eEL04<`X_f2B|X+)`Osʫ2r$b^eBF߰}yp7Y{=c8BSx T")( +QntS b_V:7cKXy/g!Hp\/&>ЍǪOv P:TX4?i';)~3^6@d ,(tɸ},@Y,؀:16 -6Zp0+ɺ ?yG<|>J~og"8l7hpK.{&)pzp M~.>H%ac?d@i?\n)s7& e!aSooB< Qì>pݷ:v<f3{e??01*֟z|B $j[?[_'8'Ҩ>oOAG68}H:Ljvy{Z$XF՜ V+BNfP |c } ~vX[?/yZ$S_*n %6\tw^z{F20A 4e_0oX -?ڨ<8l7?Β`wakڞ/~r*ϐ[%,ˇԨH@>> h7^5OlsGiqYك9.=_g灮x7nsŶ'tp9|M=@+&܂bzcc,}5h~ne+ϙ;@9/d=?ZP3fVkxֱnk% 闞φhw QQ!UQovete| %oFGTͅ_OuIoo~ 6)A$JG*StAZp c$PH-! GIx0jlc ?a`2bgЃX4U;bp7j0H9B5mu?hDkN<>!,2e<\BY~EMB %`9BCX)箶>I7U 5m!2%#(XrYDQX@YaM%q>b?sp TAlc g{ݞVABS/E~͖3P ,8@nC!/EG ד} ue|NA%@G~F +La>u 8D柜U{ݖVIaBЬ}/Yk@TQ2/r/)geF&/tYTo /^oZ&޹3$#p/oð IJt~@]̄,ѧpICsM~kx 8?~/:F\rmnf'2M>Å D]\} ~C`7͸w1~Ib;HF S_7驰=i~ꏋHz'~g8>Zwi] ו~wO{>N=Mr}~˾ y.P߮ . œu њB0l֞٭4Qo/CRVq0Xy9JF]2nYF@ 3ت[E[u{ةL z.)? ah68.N`=#~YBGr6 `p*]¿G\,+bk[x9t9@Gx+uoj;L1eGoxc/TP}vb?:p9}W&]t> bV/uoC8}>cDh9@Ri]ij1. 8#hv!!N=wC&pzcKzipSFS"+ѥHL\2Co^) (v0|SUtB4u+ w/*~kki"2)eGA@ZE)~TrB!Hv$6 -:K~2bR tPx"{PREl q C ?1HYUwA@p7L&j~[._:;ߑ.8 av; A3ԁQ7vMvo]r{w*g/}7|#8A9Ь 2 :d+ FC cBפu#nA40)ׇ?Pyݲ M ];\;_}]g{"2Lq8#|í/'y8<oݒ#VY_$DRW~vG"(~8o\:O}P] rG06(7aif`ozaO`>_w݀?\[&D6c⋛=ytڿ=N$zϝ~Yw ֟Mhk8^)0 C%x\fq18h\& ;6ЧA-:?SooPF=\@\@.@K? &lo|;ۛDer&Ʉ~U+oRכws]Ƿqڱǂ@U`/F=^pC.vQs0u#x082ܾBQhsyԳ)|y[֎p+ kf ܔ:C >̀ZRh#:nJ9\] m{&@48IG0$HE4|@T[wH%7f.9_zˮdRܠ_ٱzz cl 4o<ƣi4n66{ Q&0 V?f @},nܸB ASzo|ztʤ&ۯ=9qt`ksvڗ%׻xL2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$7l^ƭIENDB`lemonade-sdk-lemonade-d88f5d9/docs/assets/footer.js000066400000000000000000000050761515430344400223750ustar00rootroot00000000000000// Shared Footer Component for Lemonade Website // This ensures consistent footer across all pages function createFooter(basePath = '') { return ` `; } // Function to initialize footer on page load function initializeFooter(basePath = '') { const footerContainer = document.querySelector('.footer-placeholder'); if (footerContainer) { footerContainer.innerHTML = createFooter(basePath); } else { console.warn('Footer placeholder not found'); } } // Function to initialize footer with DOMContentLoaded wrapper (for standalone use) function initializeFooterOnLoad(basePath = '') { document.addEventListener('DOMContentLoaded', function() { initializeFooter(basePath); }); } // Function to initialize footer when footer is already in DOM function initializeFooterStarCount() { // No-op kept for backward compatibility with older pages/scripts. } // Export for use in other scripts if (typeof module !== 'undefined' && module.exports) { module.exports = { createFooter, initializeFooter, initializeFooterOnLoad, initializeFooterStarCount }; } lemonade-sdk-lemonade-d88f5d9/docs/assets/install-selector.js000066400000000000000000000633131515430344400243610ustar00rootroot00000000000000// Lemonade Install Selector - Clean Rewrite // Supports: Windows/Linux, App+Server/ServerOnly, OGA/llama.cpp/FastFlowLM, NPU/GPU/CPU const ALLOWLIST = [ // Windows: OGA (NPU, CPU), llama.cpp (CPU, GPU), FastFlowLM (NPU) { os: 'win', fw: 'oga', dev: 'npu' }, { os: 'win', fw: 'oga', dev: 'cpu' }, { os: 'win', fw: 'llama', dev: 'cpu' }, { os: 'win', fw: 'llama', dev: 'gpu' }, { os: 'win', fw: 'flm', dev: 'npu' }, // Linux: llama.cpp only (CPU, GPU) { os: 'linux', fw: 'llama', dev: 'cpu' }, { os: 'linux', fw: 'llama', dev: 'gpu' }, // Docker: llama.cpp only (CPU, GPU) { os: 'docker', fw: 'llama', dev: 'cpu' }, { os: 'docker', fw: 'llama', dev: 'gpu' }, // macOS (beta): llama.cpp only (GPU via Metal) { os: 'macos', fw: 'llama', dev: 'gpu' }, ]; const NPU_DRIVER_URL = 'https://account.amd.com/en/forms/downloads/ryzenai-eula-public-xef.html?filename=NPU_RAI1.5_280_WHQL.zip'; const GITHUB_RELEASES_API = 'https://api.github.com/repos/lemonade-sdk/lemonade/releases/latest'; // State: os, type, fw, dev, latestVersion window.lmnState = { os: 'win', distro: 'win', type: 'app', fw: 'oga', dev: 'npu' }; window.lmnLatestVersion = null; // Fetch latest version from GitHub async function fetchLatestVersion() { try { const response = await fetch(GITHUB_RELEASES_API); const data = await response.json(); // tag_name is like "v1.2.3" or "1.2.3" window.lmnLatestVersion = data.tag_name.replace(/^v/, ''); lmnRender(); // Re-render with version } catch (e) { console.warn('Failed to fetch latest version:', e); window.lmnLatestVersion = 'VERSION'; // Fallback placeholder } } function isAllowed(os, fw, dev) { return ALLOWLIST.some(c => c.os === os && c.fw === fw && c.dev === dev); } function findValidCombo(os, fw, dev) { // Try exact match first if (isAllowed(os, fw, dev)) return { os, fw, dev }; // Try keeping fw, find valid dev for (const d of ['npu', 'gpu', 'cpu']) { if (isAllowed(os, fw, d)) return { os, fw, dev: d }; } // Try keeping dev, find valid fw for (const f of ['oga', 'llama', 'flm']) { if (isAllowed(os, f, dev)) return { os, fw: f, dev }; } // Fall back to first valid combo for this OS const fallback = ALLOWLIST.find(c => c.os === os); return fallback || ALLOWLIST[0]; } window.lmnSet = function(field, value) { const newState = { ...lmnState, [field]: value }; // OS change may invalidate fw/dev combo if (field === 'os') { const valid = findValidCombo(value, newState.fw, newState.dev); newState.fw = valid.fw; newState.dev = valid.dev; if (value === 'linux') { newState.distro = 'ubuntu'; } else if (value === 'win') { newState.distro = 'win'; } else if (value === 'macos') { newState.distro = 'macos'} } // fw/dev change - validate else if (field === 'fw' || field === 'dev') { const valid = findValidCombo(newState.os, newState.fw, newState.dev); newState.fw = valid.fw; newState.dev = valid.dev; } window.lmnState = newState; lmnRender(); }; window.lmnRender = function() { const { os, type, fw, dev } = lmnState; // Update active states and disabled states const cells = { os: ['win', 'linux', 'macos', 'docker'], distro: ['win', 'macos', 'ubuntu', 'arch', 'fedora', 'debian'], type: ['app', 'server'], fw: ['oga', 'llama', 'flm'], dev: ['npu', 'gpu', 'cpu'] }; // Reset and set active Object.entries(cells).forEach(([category, options]) => { options.forEach(opt => { const el = document.getElementById(`${category}-${opt}`); if (!el) return; el.className = ''; el.onclick = () => lmnSet(category, opt); if (lmnState[category] === opt) el.classList.add('lmn-active'); }); }); // Gray out invalid combinations cells.fw.forEach(f => { const el = document.getElementById(`fw-${f}`); if (el && !isAllowed(os, f, dev) && !ALLOWLIST.some(c => c.os === os && c.fw === f)) { el.classList.add('lmn-disabled'); } }); cells.dev.forEach(d => { const el = document.getElementById(`dev-${d}`); if (el && !isAllowed(os, fw, d)) { el.classList.add('lmn-disabled'); } }); // Render download section renderDownload(); // Render quick start commands renderQuickStart(); }; function renderDownload() { const { os, distro, type } = lmnState; const osDistro = document.getElementById('lmn-install-distro'); const downloadArea = document.getElementById('lmn-download-area'); const installType = document.getElementById('lmn-install-type'); const cmdDiv = document.getElementById('lmn-command'); const installCmdDiv = document.getElementById('lmn-install-commands'); const version = window.lmnLatestVersion || 'VERSION'; // Handle macOS (beta) if (os === 'macos') { if (osDistro) osDistro.style.display = 'none'; if (installType) installType.style.display = 'none'; const pkgFile = `Lemonade-${version}-Darwin.pkg`; const link = `https://github.com/lemonade-sdk/lemonade/releases/latest/download/${pkgFile}`; if (downloadArea) { downloadArea.style.display = 'block'; const linkEl = document.getElementById('lmn-link'); if (linkEl) { linkEl.href = link; linkEl.textContent = 'Download Lemonade Installer (.pkg)'; } } if (installCmdDiv) installCmdDiv.style.display = 'none'; let notes = ''; notes += `
Note: macOS support is currently in beta. The installer is signed and notarized for Apple Silicon Macs with Metal GPU acceleration.
`; notes += `
To build from source, see the Developer Guide.
`; if (cmdDiv) { cmdDiv.innerHTML = notes; } return; } if (os === 'win') { if (osDistro) osDistro.style.display = 'none'; if (installType) installType.style.display = 'table-row'; // Windows: Show download button let link, buttonText; if (type === 'app') { link = 'https://github.com/lemonade-sdk/lemonade/releases/latest/download/lemonade.msi'; buttonText = 'Download Lemonade Installer (.msi)'; } else { link = 'https://github.com/lemonade-sdk/lemonade/releases/latest/download/lemonade-server-minimal.msi'; buttonText = 'Download Lemonade Minimal Installer (.msi)'; } if (downloadArea) { downloadArea.style.display = 'block'; const linkEl = document.getElementById('lmn-link'); if (linkEl) { linkEl.href = link; linkEl.textContent = buttonText; } } if (installCmdDiv) { installCmdDiv.style.display = 'none'; } } if (os === 'linux') { if (osDistro) osDistro.style.display = 'table-row'; if (installType) installType.style.display = 'table-row'; if (distro === 'ubuntu') { // Ubuntu: Show structured server + frontend installation const debFile = `lemonade-server_${version}_amd64.deb`; const appImageFile = `Lemonade-${version}-x86_64.AppImage`; const downloadUrl = `https://github.com/lemonade-sdk/lemonade/releases/latest/download/${debFile}`; const appImageUrl = `https://github.com/lemonade-sdk/lemonade/releases/latest/download/${appImageFile}`; if (downloadArea) { downloadArea.style.display = 'none'; } if (installCmdDiv) { installCmdDiv.style.display = 'block'; const debCommands = [ `wget ${downloadUrl}`, `sudo apt install ./${debFile}` ]; let frontendSection = ''; if (type === 'app') { frontendSection = `
Step 2: Choose your frontend
Option 1: Web App (default, available at http://localhost:8000)
The web app is automatically available once lemonade-server is running. Just open your browser and navigate to the URL above.
Option 2: AppImage (portable desktop app, no installation required)
Option 3: Snap (fully sandboxed desktop app)
`; } installCmdDiv.innerHTML = `
Step 1: Install lemonade-server
Via Debian package:
Or via Snap:
${frontendSection} `; setTimeout(() => { // Render deb commands const pre = document.getElementById('lmn-install-pre-block'); if (pre) { pre.innerHTML = debCommands.map((line, idx) => { const safeLine = line.replace(/&/g, '&').replace(//g, '>'); return `
${safeLine}
`; }).join(''); } // Render server snap command const serverSnapPre = document.getElementById('lmn-install-snap-server-block'); if (serverSnapPre) { const serverSnapCmd = 'sudo snap install lemonade-server'; const safeLine = serverSnapCmd.replace(/&/g, '&').replace(//g, '>'); serverSnapPre.innerHTML = `
${safeLine}
`; } // Render AppImage commands if App + Server selected if (type === 'app') { const appImagePre = document.getElementById('lmn-install-appimage-block'); if (appImagePre) { const appImageCommands = [ `wget ${appImageUrl}`, `chmod +x ${appImageFile}`, `./${appImageFile}` ]; appImagePre.innerHTML = appImageCommands.map((cmd, idx) => { const safeLine = cmd.replace(/&/g, '&').replace(//g, '>'); return `
${safeLine}
`; }).join(''); } // Render app snap command const appSnapPre = document.getElementById('lmn-install-snap-app-block'); if (appSnapPre) { const appSnapCmd = 'sudo snap install lemonade'; const safeLine = appSnapCmd.replace(/&/g, '&').replace(//g, '>'); appSnapPre.innerHTML = `
${safeLine}
`; } } }, 0); } } else if (distro === 'arch') { // Arch Linux: Show download button let link, buttonText; if (type === 'app') { link = 'https://aur.archlinux.org/packages/lemonade-desktop'; buttonText = 'Download Lemonade Desktop (AUR)'; } else { link = 'https://aur.archlinux.org/packages/lemonade-server'; buttonText = 'Download Lemonade Server (AUR)'; } if (downloadArea) { downloadArea.style.display = 'block'; const linkEl = document.getElementById('lmn-link'); if (linkEl) { linkEl.href = link; linkEl.target = '_blank'; linkEl.textContent = buttonText; } } if (installCmdDiv) { installCmdDiv.style.display = 'none'; } } else if (distro === 'fedora') { if (downloadArea) downloadArea.style.display = 'none'; if (installCmdDiv) installCmdDiv.style.display = 'none'; if (cmdDiv) { cmdDiv.innerHTML = `
For Fedora, please follow the build instructions as described in the Developer Guide.
`; } return; } else if (distro === 'debian') { if (downloadArea) downloadArea.style.display = 'none'; if (installCmdDiv) installCmdDiv.style.display = 'none'; if (cmdDiv) { cmdDiv.innerHTML = `
For Debian, please follow the build instructions as described in the Developer Guide.
`; } return; } } if (os === 'docker') { if (osDistro) osDistro.style.display = 'none'; if (installType) installType.style.display = 'none'; if (downloadArea) { downloadArea.style.display = 'none'; } if (installCmdDiv) { installCmdDiv.style.display = 'block'; const commands = [ `docker run -d \\`, ` --name lemonade-server \\`, ` -p 8000:8000 \\`, ` -v lemonade-cache:/root/.cache/huggingface \\`, ` -v lemonade-llama:/opt/lemonade/llama \\`, ` ghcr.io/lemonade-sdk/lemonade-server:latest` ]; installCmdDiv.innerHTML = `
`; setTimeout(() => { const pre = document.getElementById('lmn-install-pre-block'); if (pre) { pre.innerHTML = commands.map((line, idx) => { const safeLine = line.replace(/&/g, '&').replace(//g, '>'); // Only show copy button on first line const button = idx === 0 ? `` : ''; return `
${safeLine}${button}
`; }).join(''); } }, 0); } let dockerNote = `
To build from source, see the Docker Guide`; if (cmdDiv) { cmdDiv.innerHTML = dockerNote; } return; } else { // Hide Docker section for other platforms const dockerSection = document.getElementById('lmn-docker-section'); if (dockerSection) dockerSection.style.display = 'none'; } // Build from source note (only depends on os and type) let notes = ''; // Add Ubuntu-specific note if Ubuntu is selected if (os === 'linux' && distro === 'ubuntu') { notes += `
Lemonade is tested on Ubuntu 24.04 LTS but should also work on other versions.
`; } notes += `
To build from source, see the Developer Guide`; if (type === 'app') { notes += ` and App README`; } notes += `.
`; if (cmdDiv) { cmdDiv.innerHTML = notes; } } window.lmnCopyAppImageLine = function(e, idx) { e.stopPropagation(); const pre = document.getElementById('lmn-install-appimage-block'); if (!pre) return; const lines = Array.from(pre.querySelectorAll('.lmn-command-line span')).map(span => span.textContent); if (lines[idx] !== undefined) { navigator.clipboard.writeText(lines[idx]); const btn = e.currentTarget; const old = btn.textContent; btn.textContent = '✔'; setTimeout(() => { btn.textContent = old; }, 900); } }; window.lmnCopyServerSnapLine = function(e) { e.stopPropagation(); const pre = document.getElementById('lmn-install-snap-server-block'); if (!pre) return; const line = pre.querySelector('.lmn-command-line span'); if (line) { navigator.clipboard.writeText(line.textContent); const btn = e.currentTarget; const old = btn.textContent; btn.textContent = '✔'; setTimeout(() => { btn.textContent = old; }, 900); } }; window.lmnCopyAppSnapLine = function(e) { e.stopPropagation(); const pre = document.getElementById('lmn-install-snap-app-block'); if (!pre) return; const line = pre.querySelector('.lmn-command-line span'); if (line) { navigator.clipboard.writeText(line.textContent); const btn = e.currentTarget; const old = btn.textContent; btn.textContent = '✔'; setTimeout(() => { btn.textContent = old; }, 900); } }; window.lmnCopyInstallLine = function(e, idx) { e.stopPropagation(); const pre = document.getElementById('lmn-install-pre-block'); if (!pre) return; const lines = Array.from(pre.querySelectorAll('.lmn-command-line span')).map(span => span.textContent); if (lines[idx] !== undefined) { navigator.clipboard.writeText(lines[idx]); const btn = e.currentTarget; const old = btn.textContent; btn.textContent = '✔'; setTimeout(() => { btn.textContent = old; }, 900); } }; window.lmnCopyAllInstall = function(e) { e.stopPropagation(); const pre = document.getElementById('lmn-install-pre-block'); if (!pre) return; const lines = Array.from(pre.querySelectorAll('.lmn-command-line span')).map(span => span.textContent); if (lines !== undefined) { // Join with newlines instead of commas for proper formatting navigator.clipboard.writeText(lines.join('\n')); const btn = e.currentTarget; const old = btn.textContent; btn.textContent = '✔'; setTimeout(() => { btn.textContent = old; }, 900); } }; function renderQuickStart() { const { os, fw, dev } = lmnState; const exploreDiv = document.getElementById('lmn-explore-command'); const exploreSection = document.getElementById('lmn-explore-section'); if (!exploreDiv || !exploreSection) return; // macOS quick start if (os === 'macos') { exploreSection.style.display = 'block'; } let commands = ['lemonade-server -h']; if (fw === 'oga') { if (dev === 'npu') { commands.push('lemonade-server run Llama-3.2-1B-Instruct-NPU'); commands.push('lemonade-server run Llama-3.2-1B-Instruct-Hybrid'); } else { commands.push('lemonade-server run Llama-3.2-1B-Instruct-CPU'); } } else if (fw === 'llama') { if (dev === 'cpu') { commands.push('lemonade-server run Gemma-3-4b-it-GGUF --llamacpp cpu'); } else { commands.push('lemonade-server run Gemma-3-4b-it-GGUF'); } } else if (fw === 'flm') { commands.push('lemonade-server run Gemma-3-4b-it-FLM'); } exploreSection.style.display = 'block'; exploreDiv.innerHTML = `
`; // Add contextual notes based on inference engine and device let notes = ''; // NPU driver note if (dev === 'npu') { notes += `
Note: NPU requires an AMD Ryzen AI 300-series PC with Windows 11 and driver installation. Download and install the NPU Driver before proceeding.
`; } // FastFlowLM Early Access note if (fw === 'flm') { notes += `
FastFlowLM (FLM) support in Lemonade is in Early Access. FLM is free for non-commercial use, however note that commercial licensing terms apply. Installing an FLM model will automatically launch the FLM installer, which will require you to accept the FLM license terms to continue. Contact lemonade@amd.com for inquiries.
`; } // llama.cpp backend tip for GPU if (fw === 'llama' && dev === 'gpu') { notes += `
Tip: To select a backend, use --llamacpp rocm or --llamacpp vulkan
`; if (os === 'linux') { notes += `
Note: You may need to run sudo update-pciids for GPU detection on Linux.
`; } } // backend config for docker if (os === 'docker') { commands = []; if (fw === 'llama' && dev === 'gpu') { commands.push(`docker run -d \\ --name lemonade-server \\ -p 8000:8000 \\ -v lemonade-cache:/root/.cache/huggingface \\ -v lemonade-llama:/opt/lemonade/llama \\ -e LEMONADE_LLAMACPP_BACKEND=vulkan \\ ghcr.io/lemonade-sdk/lemonade-server:latest`); notes = `
Tip: To select a specific backend, update the LEMONADE_LLAMACPP_BACKEND environment variable: LEMONADE_LLAMACPP_BACKEND=vulkan
`; } if (fw === 'llama' && dev === 'cpu') { commands.push(`docker run -d \\ --name lemonade-server \\ -p 8000:8000 \\ -v lemonade-cache:/root/.cache/huggingface \\ -v lemonade-llama:/opt/lemonade/llama \\ -e LEMONADE_LLAMACPP_BACKEND=cpu \\ ghcr.io/lemonade-sdk/lemonade-server:latest`); notes = `
Tip: To select a specific backend, update the LEMONADE_LLAMACPP_BACKEND environment variable: LEMONADE_LLAMACPP_BACKEND=cpu
`; } } if (notes) { exploreDiv.innerHTML += notes; } setTimeout(() => { const pre = document.getElementById('lmn-explore-pre-block'); if (pre) { pre.innerHTML = commands.map((line, idx) => { const safeLine = line.replace(/&/g, '&').replace(//g, '>'); return `
${safeLine}
`; }).join(''); } }, 0); } window.lmnCopyLine = function(e, idx) { e.stopPropagation(); const pre = document.getElementById('lmn-explore-pre-block'); if (!pre) return; const lines = Array.from(pre.querySelectorAll('.lmn-command-line span')).map(span => span.textContent); if (lines[idx] !== undefined) { navigator.clipboard.writeText(lines[idx]); const btn = e.currentTarget; const old = btn.textContent; btn.textContent = '✔'; setTimeout(() => { btn.textContent = old; }, 900); } }; // Parse URL hash and set appropriate state function parseHashAndSetState() { const hash = window.location.hash.substring(1).toLowerCase(); // Remove # and make lowercase if (!hash) return; // No hash, use defaults // Handle anchors switch (hash) { case 'linux': lmnSet('os', 'linux'); break; case 'ubuntu': lmnSet('os', 'linux'); lmnSet('distro', 'ubuntu'); break; case 'arch': lmnSet('os', 'linux'); lmnSet('distro', 'arch'); break; case 'debian': lmnSet('os', 'linux'); lmnSet('distro', 'debian'); break; case 'fedora': lmnSet('os', 'linux'); lmnSet('distro', 'fedora'); break; case 'docker': lmnSet('os', 'docker'); break; case 'windows': case 'win': lmnSet('os', 'win'); break; case 'macos': case 'mac': lmnSet('os', 'macos'); break; } } window.lmnInit = function() { const installer = document.getElementById('lmn-installer'); if (installer && !document.getElementById('os-win')) { installer.innerHTML = `
Download & Install
Platform Windows 11 Linux macOS Docker
Installation Type App + Server Server Only
Quick Start
Inference Engine OGA llama.cpp FastFlowLM
Device Support NPU, Hybrid GPU CPU
`; } // Listen for hash changes window.addEventListener('hashchange', parseHashAndSetState); // Parse hash on initial load (after HTML is set up) parseHashAndSetState(); fetchLatestVersion(); lmnRender(); }; lemonade-sdk-lemonade-d88f5d9/docs/assets/logo.png000066400000000000000000001032411515430344400222000ustar00rootroot00000000000000PNG  IHDRpXNasRGBgAMA a pHYsod6IDATx^h&1%BE؈hK){ E@zׅe{..le?w͛_3sf̙>sĖ[bKl-%Ė[bKl-%Ė[bKl-%Ė[bKl/u͊-f к&մ[K4ѐR(oύ-l 0 C)%źD4c-߶< 7Iѩ[Fe-_^G>M:]tfDߕk϶}y#pǠ-62< $2[:CسIHI?..+-EC`gĠ-yGp-!0@nXL/5KMfR뼏Xttt>0k޿K RCVţ8YaʗH -6ҕRGt5nQ5޿Jb]a[D|,(| 0ѐ p{H$"]#nn#ֻM)$]//^rX B bHPDC<@m z^`OݥۥG_HOH~+.W%}@nD|,  }M οSsb%|1"! & 'Ł |D`"2`(`?K^^^^^ y!]'i7 a^Ok' ο"Q8w5-%|bpѐ 82Id[ _}tXJDd%~SzWz_+4("~{[k Anz$帣=O-?#^# j?|ce !eC*r"'^ ȀhDLX*FIq!Xivl?Xb iPOI|R4."=ߋ^ZψF 4y)>~Ð{e<r@ ݏURzFj`?ޖ0?'JS i4K%^=4MbI'a^ayhIy聀>\!Sš1/; s 2ǖ 9'O.9>$Է=$TQX <8Q&$Qz-f4G? O@'ECORK㞞 =RK  kI I/?=b0X90P:$GU.$Ro(@t 8px2Қ(kUDBb['=F5愡ސ;Tp74Z*84`"~kJ#p?O |sph,4^#?{1 '89\p"C$QYMŃ @ Ǐl8FJx{^(.ElVb?D}">> =EBlK=5ʗ4"?}  *#0(GCO0A/,~ U8xM]. E q" a+D\ $&:ܛҖ(E)#o a| E C7Tp3~&ahOEoA |':HO/9&I:a @Ez>}-("'sb9tD$.,r@yGr~dJ( 5m+n4ӣG0HO2s|r $jY-JaXm_4 2^Oxu;_X8:qNhNwNbq!8%>%ʀ$q|DuȉaAN? '^/33%zzU7N`0zfo|XX3ʙ!a@\/j G}O!}xG65A^$pbܺ8D"I(9\"5r)Q&$!""(5E0CJ8^ij;1}sp}|~&&6)mlfFg:[[N[>[ua[sQ[spqg6A[uk;cY[A@OGns~~X4h].,a7D~'{U?- Y7,{Zښ0tF`L{HDs.0r (*5B^:I?[7?}ɑ/|\ /[63a vE+9ܟڃzQo.x¶ۆC[*xAw$}>;?&HXp= m=]krxh7\#N(5_+à4"2ABJW7R9@{rQH\4.(0JM;J:λ;[v].ow;egS_.4cMOOqq.@jSm&s']|GC@%S46!Szbk;:CB"I >Nk ??-ϟs)T[z \q@?dž*Ǻ>x>kj㷵W65j|LSo7ۼ̛-ΛM(&A {dwnU[{9/itJZ䘣njkak߫C5A yzNDy6m  ⭗/ƗiQ7/B 9ttt\4.CR ɧKiݿxקWg9cbpe6rC}>omkl`w) W۴nOOf`syg^F @WJ UWٲ]mE5yԼG?>Kn5^:9~|RZ6gdsw E9={сW(p9o=33/9mX:!:nsz,C?5|7,6\/j*P!y y+5uR#]#@ExI* \. AKk{_~NU?KζKϵϷ+/V]l\*/GMm̦6~K;dkJwi >ZfJRT)IV$6*E.(^Im;Jlm-֞{qIGzW'{g|3c3~Џ z~Q-+߽} j%RZR+־Yڗ-תh ~=W=3$GpwÑ(Sz yXz_YW,1a Fu:#x<&"1[̿, …%P]iݿ}Vɫ~XμYg[EZ%ۀeڠېUٰW؇k؈l&6j`njm6 [ĭ|/|WNOf3R*좨Y^ m;y6lnV d7٢ mў6+~UGثXNijt9;/SN'({1 N8 &~#!>{ky lKlвKmWذUm6bMc}^/77 61M* mv4=*AQJVjm3S[7̍mvFCyD_$s?c_}cZdTYJeʓ;[ej'çz SOa)L=E.W fݣ}mXUVص*ia#C^k|nge}U85ANdX  5TlDcO\kR,sj0$)Ĕ;o(Y1ێ* ~։L `ď.ab=oKͻKޜޞu;B5B"R2r_߆h`V6W5f6j]Kok6^i7u ;-}]l45^N65^ڦ%ojSٌ632ʸfgdcswH<lݎ,)qUڊ+eMXQ|+K"el;NK{YPK9@z GZ7Wj+Gm>>Zrm*i[s'a?Q|><Xg '&v&= [Z4DX>b|0@`GvhMn{^кMYW{s.s//ـE۠%Wؐe mF6|ecFn.[赭m̺+m܆6~cG}"+H&o(So-R޾odSIش y6=l}LY3l~ȸeNVirȳN)hLT&B-J:yZ7_k|mH9$ |UHik?iJWԡ;s7akQ~DgD2%+s U5fSR,ɼf29&rݯ3 n!F ;9\`8R'M7zkڕެF{v}3r_p \T/n`C4 &6|E3\Fnc״1kuq; lF)^ VͥKmr69C=|MN~?&}϶iWXifP!(|G@.h"X!m-X~ϓ:iڶ@)~ y /Zt֕iz+Jjވv7\o~:!lm\^ށ'1=S'i !3(G0NXi'x74N%ر1L5E)']g8;%ӿGoXδތ6KQFgNC7 X.ld54Kڰe-mĊ66jU;]>^Qw"|'[ I\%'.OϑβO<pjGNX{U{.Vĵ*KP*yv%[7;yR>KaXhB^(Ћz /X0nn{RT Wyؽ⽿49Gߢ$Gϵ5 ZF`Wu1; N^ZQзQ<}y+l sl|Y6.L8lS^褒7Zz&\lŊ'fpF-Pw K'9ҁV\:Q!Ћd[͋eUJKy.Х7Y?oz=X*!P7Xy |\®^glMmcifNw&1BʠS?f̗xT2x|%,2^"':lc.};j\mwg>ZY-6p^ 4 YJa%WڇK;elԊ6ze `c׶-.7'ec7ic6~Fo<>Xzj{{Ny/g',;p%[EF#E\0A)H^Y+ Rt@FtPʓCֶSޡH= z=䂹'ҭmz_*zPzyjmFZvM\}755+Zkzjw;Z? y u s {}0 ͆->\xXt\-"6MUG,e#Wiyz8p6>:=m%9[/KJ@*{s#h]ni{}~)G: FtPA/ae[#+sǬ^W.G+jj$i5{3i.o]:9mɉ /ҔK"2~:`o&hli[#IHAR`?ձ/IFՐ ?UzLj_(Xi?9>4=HB^i>Jդ؈'T R^HzV_#uv|AnuIRT/&;CxwFdӭգ#[s;nI_{u/{olϪzZ=m[lЌ[l̛zAMҥ6tх?Tsؙ| ;rԥ黗N,G kA…V|@Ԫ#g)h,| )H{}zm"ر0AEPU@ǣeQP UIG^OTP z~n5t{/4=p)ǻOw!p";l_a:#GdS;CS%(_n]"w)|OϲG^g]ҝ6`m6hzOnu5]?4?3j{~̬SN>QfZ4沲./ٗKJ[,LzN)[v#AG+jإvmL*SSыz@/TYʎ Ɂ\pWTzhU Wu J|->k_ߧ螭ɸފ㮯z㙆Oq> N1 dXX`1ݣaH;㾝w)qډ(C0PTNGx4aziY xwrn;noM w6}?+|+VGqz]pi\^wevh@})}W ; `vIG?FOx #6&(H/4bA)SJ,Gi9ք]IIE{^ZLQ(Q#)RPG=יvu+Ż֭ûQVzb%v]`1rpv`$5\Vr`_Hx8OHg%kzBQqkW{{~[S_cUƉK]z:'lg|v en#Qv&o\t k2 #CݟSHV9wNx,?mDaK}mࣚ ght{LoRv8R+M*y :F0X0ҮvK{PTeՃd_u xxK,G#" j^(ޫL ㈶+UC)w/)9,+/+KeFkL`RKjm;O?5q3]L!p42zoU >S_%Hʄ;_{8|;]`/5]%]&SP9=Mt8gmJ=G'fMؠtFr+/dAZ W.ЃDUWT*3mKK@;C*R'$ `ˈDm`O|)S8mJmK18v`'wvѥ+oHj뮭O/SNPaECO7_Tqثꦿ TT] 7շ#[e O5]fLI_/i'k>U@ڦ|]=@Q= GQ}o/_`ȫc#rn< @^$KRh]"Ku.}F` R4bNc)RqG`gN_Kl g vn Oֱw]9nkMѝѝYӇ;3= |rBd$&3 Π0Jgp9 SC %0픓LWU4aiNhRli`44"bJN(G{ACLG_#{zvET5+jk{E.6MFMѝs][t2?t윙0[Z5CL) Cw[H+C$xy ' ̫w S [I0;Ctt{uT? k|dߊvx}C+*hL3C#S;^ S-^=vܗ*jjO'r+0B C`/̅Pp j^/:4mX8"_ՃҸ*WQOs8 $YvVe\޹wWy{hDw*3veTsfGUی* ̐(D=)Q2cmQrIXI$$QѰ#wͫidEZV]p& &03v.]aI]h&P@zlwU]l`#C_Q= sA'h!A|XE! 0 da3~5%lSL 8< ؓvV yI^)5E躻zq7ןux~L kxZ0Uʒ|Iti >} kCKR4y sҨ؄gND2'> {9upIMFVUnSDOɂ2MPEbgI;\ <h#$;kY6xH`)ؙZKt{^O9`>>ޥ a @&_;GO!ss=Y9OP7;6;N!`Egk:`\>2/a#Wih|Atgz8!$4̫!Z`g"Aߵ_Da P)w lK6f`߫Op9U>(s@_-Һj 0ear1daȯ˯{YHN#5vӝg7 (ŷmhM'{ύ;ڻ+3I3 x E2s/u!ʓz*6x:n8DWѝ,M tt_ם/kACk(7QT 29zF.6 (."2<Cm ~5ǃK3Ob8>Sg }`uw s /Xsy~F@c:؞<5rdar?r50 ܚ̋v'gp]]iS:5ۜsfe$8t3pH1*=1hGy@O֭Mx">Ot'B`e8"adzsWI=uQ~ΊVoI)3t,%j;.@C BG'jw =Im}dLAȺCيd5vrQt+X 7GE;{N. C $^A ~1#7ًu]y1 VVd/>qyN3}2LLZƌV/CUzrcvڀ5YN 4xx;x3Ndwر1ى0فگz7([J6*oHL2$S5A3wW%K/[m9 9d}֖VigkF+=8># Rt QڒgCsV&GhkCkD2<;ޝϡa<8*9'툛ܬfVUmSM)@![[vB ~k@x|7v@-0RܥI9R9<:Uۦ 4/]5C6Sf)Pݩ(hK{^Ww4Zg?=~5>gHfrY]jhMcviv斲2m,gq_~ȹGw/CU RM%21C|iA(vV;i N햓~ 6':;GNػ?czefVEQ9= Np ` ཙ0I !QK0S]Jվ[ːY;.[{<j7A'{Ggn٥]$l}/*BZX7z7N @mia[Y+mJrKfj cQmL%^Iֺ&E%Ej4}V{opݡS.[@#'zIx" gP;Ҩiس}2a V'F`kaUZZ61CevNTɟne/!haM2ppL[ }=q8!aD=29DJQ#/3Y &EJ~Sit.033j!w 9[@B/?w z[w(ge>;ԫ`ٲ"=S1JױR*IvٚtܺuV5zDխ ӫ2L OÀ[cv_j;VANDvT1ܰP4yّ㎫.X*9m. a)Dl ato*&;<ՓLAB`; =C`OK`UF.@I)Q@%$i$'Y (ENg 4ٍt >Sf yN!kxlz6ﯨSgIŇmYP%Ձm o\ WU#ږ $XJFTSp bx{t +8a;$5rwSb;'+1v ǥz¢r.\e&$ו"WηG/E2D'D jð攓eLՠd]kڢ K-lA'Eg DʄZ* ۥ⻵z86%vz.]`K~_ L1$ }n"rO7]}F|h,쏶j_Y~4X? GPձVX`WTU U-,oq;yِzUMLfzgL Ҿ=|xvTljb2=Y>]$sb~Tԓeۨt. ΩjD8` +?QQ:L!`'Tـ$i@X xD;ޏ xA]m>_QHRJRmt N@as2Z )3Tw2L/#r4 +VLO?M%RJA݋X`g9oje١lȶG"5w=62<}9vn!p1Jq>𵁝j Ĩ9 YY캠Ip}`kG$:ACra}$ $I"y5Hm{۴6M $^ %^ūlql~D}N>/I-9|Nȩ@_ ,k mGu'Cғ9n/ "lN  nK1k?Tz(o)=`:rlrdz=/`[;,65 ``Bݲ斳uZP #Q2#%'U a}|˱,A$@H 8'жjvMnӱl~5ANԱ%8t<@ as n@j~J?Eǐc|I,3QTaBQ}@(MV6_3?ne£۽M8JNRavSrO=A Wc TanӅv".`*0aWt=٫Hɀ$Jݎ=@DpAz--Ni8m' ƭ}oϡO0R=ۗIns$ݞ|pɟ<}%I 9hLvv/=RHuvxRڻ\_Ns` v=$$zxHL-H8xAX->op[mEq2NݪoY>^n@6'K:Z7Y$k{*)+[qпv|#$Btѥ?=|~nZ]ͺxGv(Dv%|aQʣ‘ƀNLiDM H4QJHTXnLe6@Zg`,7k~EPnxE[6}&u (?DH$j}:ڞf j|AYT`;`WT&#Fzzim#\J5Cڞm'/?XoI*zcn-e$~&3܉2]1ܩDȐ4.3G M[CðROkJz^ c bP m$4NC$\ڤ7MZo$7iMfok`g>z6'OкAmtl4ضkߔ9s}ePxx@gt[{l`_ {;MxYxbv˓|RL[I 6+W<v1>DOe#085ޱwv߾6Q"3+] `D4H'8YPax V' Q]z}#:FA1&gݬ}n-w> "o@HVGk]xmKu'^8A TQ}VΉVU):C7 ͂ADu[}i#;V mUvER<0iWv&=Av}/SDyO$pZ o~\@q#yF5eAQ}@ +h`_' tKtUZz†?2&??|aε6@13ny´mW~BXMSz+#Yv NrK#ˏtţKi .8eʃ Nݛ2!eCJymq#@zIXKa9+Է)1ɨ'V nI<7J]( Zi6F봿hUOc@Z?ӰJ/z}˾(3_hzNST_' } KɾT.m`G7}nC;Ola<~9"s Lp[OצTHHEqx9nΥ ,ytLeILzeբYuB@|Huv\ #*]Ji YQaF8)Q!I;S/ImI@ȑ[n mHWK[^ ҺAo}אMZNQ}:}u tfk 5}@_#*d4\ЫzŒVo`g7=Yڄ_.ka}B1׎˟%~Z jqSR#gP@$o^[Vn`&S:Li)/ yQ:Kiz/Ux$ xF4HU-Mue`$$GrW#[-m[-uT#zAa4}}Z}}@_Fe@_#W˾kE}@_-W!R]ɧW E Dm`3),u\3N88` 3! ,WoΌ8`Z;O"CJF/᩾d~Ʒwj]w-0R3=ORKQtOzɊ&A Sg0 C4DaGrW!j_g]mH}V ՊV{W+}Y%W)JEU}`_)W@.-@J֥BY 3h`&58|7GׇlU,hM_ ^kzoL9e]SJ}O9>cɀŁed$8)%)bzO(vM3Cz8BHʈxk,~ܭG0+]ڮFf}JRY)W*TT_)W*PT_RTT_)W(rB/ː@_&З"5JY2^2Ωoy3 l',it3lLm= kv#'5y2|?eߎE>F3lay$[;(aO4{>Q'v*㴯-~TH億qR y_$ }`_Bg }}`_\/2Ee}2L/RT/A=\Z$"e]"~XNo{'7,f//ɯw\ٿvl kѰˏ^HE&JXѾnKgyUmԠdP('7ED`.I;U'GOԺ ~[8E=&s )H0I8nO!hy!_-L/2}e}RT2Ye}`_* TfDQ}`_*ؗ%}1" tRѼ\V2[Q]Oo9SOXFf܃S'Ƅ 8 *ԯE5a{tEƓT9OR}' \"zv#GN8.}k- l`t!uF?. N"h{^.V'M~zAN_+H愈VJGA)-tKRD/wX*ؗ(/KbX/VT_, ł}`_, ł}`_$ H/ ZѲ9/H-{\Kȶ mnO<ѰK7+upEƓTN@1x>ms JۙEG6<ݷz;zs-*dOFA=X@٧JRwJDukm~"|Yo~_+HYҊȑ_;ZGbXǽX/b5ł}`_$ɯ/"P/ B@/  y]+42%XV zΤg|}G4]WpoxdnyGTJ/G֔r?܊ڙdh+3~xҠ&\r&M1h({RVR {v)Y'D km~&kz~_UJABZ.24%H-K/q."HǾP/XƻP/ BE1 >_/|>O~`ت4ٍb@Jg P@UD7AQ}l}KmؖjVٱ;t~9^gw`<0H ^ï_݁w^S`3QN3/2/][IɎuF;-ȍ ΂b ƲT)RNz?Ao׺V'7kdk6:FV is@.-hK/D~_]  =.Pt|dy}TӯM,56#aL qj29>^q;Rw- y*p  )@`| mZSC,7 zEu~_V riTZ"-hQD  HǸ@<>Ou<?W}\E9s\>Gsg ق},?KljUR&V>ModYV8{z@O?hd7AO7(>㱌{x"tM`JmP _a>SN+\ oWej`my]=K mh557ybȑ°S-cJ6A ?2ӹw*hk>ä0p [hj$vv`&A0?R?O$AB />U~OvMmqfߨ7ZAFЯV 2i EBiAD<'\\?Gggg)3 E3ݧ+Oݧ ijjK*4V:Olb4YF;vˣ Alsߦ͖U77\{ݧ;ׁ)Z= }/vN VDzDߪWkZ:MOSݧ)rr++&#['ͬxBS+kbcT#326d_[׫MxU}E~̤+jR;?imeZYV2kn7cZ視;ll{G4a-Ɩ*]oV^~?s밇k_7jZone%xJD<*5Fw4Z8 m"jIFADwv/m{[YoPi~JЯ iHZ KHϑfKt<L?S1N445֩~E)'+ORt('vO:ZV1J+ƷqmVV4iaFQ-wd3oj6!M,m`KԶ uo/lJ \'A"p-yD0W^pt V&\NK$ܱNT-ːGEVc Y 1 Mm.h`]"W.KVEoֺmN>VK+ ALZ}/ iia4Shm*긧~$?Q}D"'\mUXV1}||mgcZ6V0limkH31hj}Y|񝖶Sݓ8ɗiY(J܅sv[VrO DE>}Fp~\:io>4R:èh6H "(aOxBE/mkqYm:AFۯ+roD/J 9y\A?G-3:MSu|Sd5I~DEOٙ ~̸kj\Wjv#c:ZV33!^$SCːGE;uGG=upF<"$~/PIBq`"+#mqIlk~]%WRADZ}/ y\i-͒fgH4?U㚬,K3I:Q ?^St';uV5ZsUje#XGxdG+ >lo_iyYжA7vhe}[[,VֶA}KmW,?0~H!x|&@J?ʄ+5wF=2$ѝH bs_ ͗ 9l},i4]O8)}kD'~x?^}XvfjL}U>fGFvW[]pxg+ hyt!-go`;=2^m,6v]7ԣ'Fcx0s~ݧ ߯4,Lm*с ^d4ѝk|'f.}mPkB mt)-Rq!ţ'm6M^U~_&h_~'g̖f ti>{4EOֱLu\&~8?V}`>fuU~tUFt##YV8j+}tA-g`G׿vҲz_ii]io_io\i_mok_h˞&=uU啗sb8ga~70{A1`/a؉tk5%dL9x?!>=k̠ڝ+ZŠ&E ̭ |` Sm6NZVk~]*h?~4W#gI3YӥiT?E?Y(?LqcE1~hEQ?nʑ7[ňvdV2 ?fCڡW[ޠ,w`dt-WGK%^h^l+~-\DuG[/9Lg~kaa|K:D%z?@'}2w2ELtH W .fN휲b*xѫA6Iz}FҺ+ri_}, y?[)Ӥ)$D'x?NchEQ#E#n{Z[lMvd؍V:pnC,W{sŲ{uv;[-ζ.lߺْ?_gcs0g7*7#|%<3e <"/\!m{~tj=Km[-ШoS?"PIB[2?)Хi^[+ LZm |?W-gi3i)$}Dic`'?c>J#HV56+ӎ|pɊ`EzXЀ,ߵsղ߹vueyv%rm{j|7[kmzܧouj{\:pb꣦ݙhmk{mѽUbGw>@w:Nrig{r?T$Z^?7 Mt,pؖ @_`VjZwX- RPt.cUC![۬tЭV2 ldh{`a߽}z:zw|ݒqm:7ڪ?bK>ֳpbsa‰ֹ_S >TӨv~^t2Ct2;w21LA7[^ЖjR:^n3<}.H,ж9,i9MO쓥}`/? т}`H>P=V9n| Î JfEn¾=X~{7فwooh{߼vq|e^_FMؚgoeOe3Ms =:3= ^ׅkNLI_K{t2dM=\aF>g7r2L\;["P!+(5Uz$|ZХeXuj\m?[)ا =]DKch>J#0E~}V5tˎJa}ooýoC~byobߺq~&MM--7?M>sݽ67u>t7){g8ˍ]'+kY[m Nt!oݙM̙/EBjgP+/޳{/m]`|y5Ѽ:KbPΗj9}`!ا )$`/? т#>RPC}V5>xm}>wZQ;v,[-X=mk=m=-垖bOK'ܭOa럾VO[n*:SݣOa^S5EuLvkʌO93HV)`?V;]M|)OVeG%3K <)]!ا )R4^KG=].؇ `i~@׎J޿ۊzei߹߾y~{6⭖­,[ҟ?e[65h5 _Du3 mˍ-ѽ6O hb 0v܉@__n`ڳ8=0uXJH: tEY >M+Oq}4Z$G a}`"K}vV+|n;]v;kwW=/aqx۝,Ow?kqozQ՟؇(kqy$^f)w^WzVNTg@0kYj^iT՟ |wF“dg:S+'~^\[8OYElA|ܮ!K蒃>]O& =]])? }4X~ZyV}VνVֽv{,wﶽ/m^vn=ܽ,ضgO?~gjUlpo aQGK=ZTkapkꟳuwFUsf"F@ʭ`a;?} Ʌ jӟC\i*)bA H/}4G%g}4YO8i`#GI#{4T RCV!+}@_gygg9/kq~^۽{-Y@Om&iݳۊg7nTx KMH8Jq0ZL k=;R%{)ғϳ3=`g[}漠 'Jsl@fi}4YO8i`#GI#p?T" RG#Va~;vl2.% W]?EVussHQ&xIJԣ"Qk_WaGN $b0织UMv0d*Mh\X莎[7NM }zY@]` sg-g tA?MODi'?cQ~`! C!}4PԪ*z?"#l?l^}^~H?d_x>W`GmJ@,/wZ n pmxM] { s;$'HR~Gu=jRwR$*SH=YY^$Rԃݿ3E '}^ES^/ lA?K33 ~4I"'qc?F%G pA?T"K}ggVQ+Q@Ԋx ^@@xA?/~`ȯ.ob/ԗZ+-6O>9:4735)=+b5cQK.a=%¥pʉ&pILDKr OR~#%BdT(5[a; PacײaGհ  6Ew*I$4w\uMoJ>~&)eo&?HDv'x+G Q4B`?HOU~\?nO 'X#o?io>iů?i>-_Ö$L%Hofohn{{]m{wZf^3[j[}{9잔ձw5>_L1FؾJ;d5TgܿSx]9IXޓV"|nSNNuY?cz pE1r?J tY,P?HFO#Uz*}~F?˳E7~fJV μs{{dn\m{t>Oa_|ӊfVӍIJ1ٍ>RJouٗqʼnհ lb~ ;!̟ aʬ>'ʅmMQ{)wiw8e߯[5A.3L}A+V?@z s/ɾ<'"Y'+z+~7V/TkiV>+sl^g9}ھ>lWvջlLKt#?Gu{ƽj_K2RJ~Do5>΄3xF$lbf$LNّESiLs[S[? zz}_u*H{WV c6DyG+|]ɨlO?}__/˧,_VTYIV+KVVSV~ZEZŀ|CvdV>+}]l,v+;8tquNս=bZ H~5e]:=(16߼8재FWg|*A$Rᄕ Ó]s.!+׊i2}۾]-.Vg[z4>{1:3ZօY@_,ާ`Q7z)=a'#n:TNMzqg~_+dUB;L$ O?2 ws}y:fwKz@ӿYGuu Aof*?E |ؿYj5x,'Ti(KKxLMdc(PЇL"խS7̓Nɸq=~hA~*mOIW>)U zXv+dϝVNO-6ZpbQSSKv2/͹'脽zl7,; ۙhN=F$OD15zgֆ@G/ ju 'ݪޕMɑ! N\Q+|JTCoe_2ՃG_~Yv.^>5 ?z:,̿y>ڀ'iJuxxdbkxGy"!s@ =]> %;??x#I_ ~ʝ#7?U|U }JJvϬ7=jxh QxC׫{\E^-[|g{ܔXN[{SN{r&i>;MAXbXvDd 'Dh\(4x֐pakz'XsXGzJܞFO)DODCh( x"߬!wyDۗe{uv=WbÉi %ݿx<Q@k:a0Ð0|61k1t48DuQsQ=fa.IREm #(gt/& ={C @pcC>H%'SƤP* Jo4 V? pFNoAј]]Ow1]H~RtTZxS*s[Å GyZ2 l=R%@ OJKgpF@@C p>0/_;NaDsɾi`|&B7kH챀>7/{aOt(BR;vkS $D=,Oe Zx(cRS* c#ƴ $U|/#_#`r"=}Je(ߓ\ +0_<ö&˻! yns{weCL=RD| ~~@yx :^ ^r>Qٓ_JjCXg;Nozz!wt8r><)%XxTsށ)ʻ&DC wu G|ψ#Cxc@T=h T|K/@|x De*?7Z5b;rzl = 3 r8o%> }8=Y+cs<2* Ts1i&QIvh?ZD}r">:x}j䗲!װCz , gsܧsbx (&z yیºwG0H2CrK jB@ol4W} 5~?N~ֈ>2_>GyJcv=f__,ޓY@ > ?ɭ7?(½9P_>xo{C'O*r M#a[K(ܺ8蜟hc/5mmz7t$To<ڇtC Í}?ƀ 0r %>=SǂxO;%U@9.-w蜫Rh߫8&bu~7dG#xCpDC ֖"5~w^\X ^aQi@"*24:#DZӘn|_{-zߡaGhd!di Bcb,ŖwG07"ހ@Oe>^\"?OkYmh44bDr 8.W\|["A_nu‘nha+9@8Rߧy4!a wqq||_?1пK6]FnѽApՇ6"|?wޣ.۰-cؿCgsh-(| hk& Bt|nO'BkXَp H78;ǖ qԓ^"JԼ:pѠU,,1ŖraEB~h6;À#911w-% |m CVwSFl ;kl- E|^6 xĖo_°0 ~4F~/ ^(ĖDj/RMpϊ- #UM-j h}ъ-Yj6Ŗ[bKl-%Ė[bKl-%Ė[bKl-%Ė[bKl-%Ė[bKl-%Ė[bvy'!wIENDB`lemonade-sdk-lemonade-d88f5d9/docs/assets/logo_512.png000066400000000000000000002417431515430344400226010ustar00rootroot00000000000000PNG  IHDRx cHRMz&u0`:pQ<bKGD pHYsodtIME%%tEXtdate:create2026-01-06T00:52:08+00:00S%tEXtdate:modify2026-01-06T00:52:08+00:00_j(tEXtdate:timestamp2026-01-22T18:03:28+00:00PIDATx8.$%Iet[<|{fLO2HCꮪ N$%/^,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,|X??2,ꏮ,%m fe2YRJ 3 c*:"ϱueY`SXS}N\Sf\JA9,2y,cJOxlYf%*30˱r i_88@@qS023̒#caN)?fALr26嘎(z2,\f0KYfeX9Fa rL|xCS28H&eY>3WT(Yٱ)aINH){?<0ܟ e[c4~+)\/1sRky*y), r*-QD&m,|YfLev<_y 叉BOǁX9w6K?,kSn,wS\ ˥9 S^kd "S9 , |& $&?'E|c13iOR־Bu"r)_RVox]R&X@MchIM`L{|L,򱿴E7Uey:TynY?wi?1YS @L51w+o+d~T;cYf]dfx9F(\_ZC1#0e@.r)[.Yxh&b[ʁPc^nJMms>~#2EKTlr^o sYn?}}ЀLs!8*t >' >vTqu 3壓X_j( z8>eJ?OE~SlDeJv{J Q: 6XrRzVF[ f 2O?sf}h,._j?fsޓh)?C0@f8׀BAapjIՁa9R@Y>)F,M 5 } >˽7)O-;Vk88]\[r p/8oG K:+YW|xΡ9eQM{8 s}cQ rbpiR @lI 70T5\U0e9-tdxs RifE Cqʢ7 +2Q1#Yäup}[os#Ue~SS;HI]rVJ fV`.3xztB:f~ɷsʜ W+958iBP?cMb%7 r1uIbAB%%P!} % I~S}橙1:_I7ucK}UOpn.@c?9+jRR` }< bmJ Jc]3%[fP9KY/`!ʟRdP9o}(19 *X ϙk:%[fxIM bt ǖL@.(Q)?T~@p} ӕ;+gN7c <n+t p*8 )3-s S ;GAm+&2 rO*rck鏙7q.ȝ})NW3=V ־[1  "aS⏹o94?!83%[fpX6uB_OT\ڟ%? @ OKn>d V炐XǺ~9)07բl$<' k%KU &bŝRR)o"u8vg Ґ)o6J+bqu7?ʭczq|W?tO/e8eynjp9+ZYaXʏYz ?w ?/ }2[EpOJɵ% l)XX 2uIs~,@f/9TcR=7᩽s'q@՟?h)Mj!IԸ'q8 -\ŕ㦉c#XrR b;g* OrOLs l/gǽO\`!L(Hϡc>i@pzr`?-9->d~%0$ȹ |& 5CXܴ1P{F K})[M X`7eϬg&3HKJ4?T)+?cP_7g9@}$?.w>4A@DZK@wh*pc!iŸtCE?L3+chcR.[$JmJY>r,1?WS+ LS}p־/Jx"p,CE~rWN8se@)?\QPBS?%TN.HX\\+=03pW1^@jmxHJ__sy ]$P!PH%`7T%D/ONycn=rh[ Y1w?8#9cs:a,%MT1 @^1 sX--0OP-K,}*zIǀ@ p^㏱#Rǹ JGDDlP(҈*5]Z5`XK`!h Pg[p@=hh08Q,k.)e@\|Μ?8%tD\h~s29)TZ}Ր\JY>PRV~F'Yv$^&^sffHX^6PX JB,+EXT UQ!)A ) h'wD}Kݶ;{KMb(cULaL2s AbmIrI)XN(}I1?CmȔrsͰ1_\_?swL]d_Os~҇oR\z4²RXi\h>5juTR˕jTDUV v4 @];-vKv!Y@y@0EH4_ &IV̿?þ+u>Juijpjuə'Fϴ^j:zFX)U-XՖ@=Vh!} in{nn,wꚨ-5u`M,))Eǁym*֛;PW$[s@DS}@Kԅe8MA' P)K6 |SS!Lɮ'VaW VקZό>9j}ɹSV'Z/J-VA鷊:*l m:W@vv`ᾱpnojp탵> Bu)Cs vxRvc7%RR;3ƀ@c V 8]-0OX+jKJ')X/gؤ;}T\F4jq}f٥ׅxQ/92RRuOcQCM[FK#( A) ;只@ !!a @hX<4ᶱy۾~ˮ~ˮyˮ~۾yom-٦/D\c}*w0 O rʀ 8 䦑MQacr;nŇmvpSasnY>"y tb39 +ع/+lĞS8_ZVWƜ_ژ92RT 6aeU?P=@T9n(Kԭwv[k7y7~o5o~o5o7oMl4Y[,O`@Vh)dă@VSǦ2!cJ@ˍ@ t38V0n 8_# 3ϏQL+ql2 gǖrT_A)ĢTXkurfɅgWƜ]9\թR˵ҋj (Mh4@7W] 8J^T Ӈp<=Э h#}cj{wonwov_͛_[P bt6R0! k Sܴ1@.cs#R $E^G ycr|uAG$ϑez p,@.v"-@V8HeO E`I}˵QF_,\.W96B TQ26ZnͿmuDW u ]8xRFPAhdIBX v mk{~omf>xnv[kZB@8<ƅ'ʱr@J؞X{s&JJ%:^C@3c?Ǡq,dkW^L'*;/s9_2OsJalǎ?Qn>*7YWxi c_svY'%$T@),RNΪ?j@u8p@Dhi`k';}vg}qW?m޿׷7{o~ks}raJ*'0H&5""j@/TPVr' [! I #MKh-e4ߔ [;W?fiTr־V'>j}/^^j>C:AX*ʒڙ}<>T*P!!"a,X"M}CmCٻ;Ͷ?/~yؿy[߼7&8M?"OQn .q~N9;Jڋ~ð+!l1u >LRc,nᲿ) cmw>:&U kN/xY-EQ\0\jM5RZUO ?_5 8O tк~ t4 ( MŲ\7xvQ݋vyza._ܛY+VEݿ{kvlr@莑(Y?99r~,X?@$ [>1҆l}w W pks pg0?X $s'NL(@|)/J-VZN:kϯ[J+mNQ/V`Q*MU8s;K+_B `pp$hݎDP-j%. W':Z諗 ?Opl.¹rSSz s H..npBWf~%XS_*I~-Y9BMVSSg+Q+N. }/|i6'JP/רj {Z_@"h$>?(z)/u"~X["uS PBZUV.ו:\_,/eխֻ%k%%P8 -Xp!B@hsKcs`29Kw n>Aln@<AN0r]ϝ/w勹8Vw}_=Gs ^'C 3qH;_b+bLH,-c.s!>#11_?\Eҷ J-Z-OyOύ>+mή9 Z"ulOfz@#kazpTO?r KGN!z-HA^w,9)ZXnV΍Z)0FbYr]g_n7ͻ=w/3!fvn})GN|x<0g-3!Z!(~A[ԗ|> #R Osh-S)xmQ'\,嫖8P2>P!PPØܹw*k>Zv! 0p>v霂/W %REijUѧgF]Znv; 4vS;I~pLS[pq>.Bxhאf   a[ `yN@lfXJWb{!#5Z#uZ\uve |^n~mN/P/ר`QPᤣ=Wt Ͽ?(Z[=(Vz<4pX71NyxJ}oct)uS 4 ppe%7B@kBEFvnEи>+q@lRӏ91)Lx\R|H> ϙ,r ҜH[) 3(bnT~ wLU~ĖJּ9H%럤5ȝRr8h(/[#MݐS@!W׀0@۬rթӂRj~g,ecgSVerc <oc 8@5kjO$#a_.s|`'$*?@' (isRS˅Jig_isⰜϭ_Q1RHЯw}`Q?Xp4Đ7=E͏?7yJ(ʠ"$"m5){x+!_ql 6|$$Rr1mc2y~+ 8?5cb,xG(9Y~y $ Bd(*W)rY;ژ+ϼM{gJ/נjAhJ@(`>폭\W#>Uz0Ё,No`ِKw .:.90 @`WրVTmVlAAd-!9ɥ}n|Hc2Ϲc 08V^zs8 Wa| p.gs,\!Q9>fc@:۲Wk@m ՙѧƜ_sMJs454EW=(p`c[~o}y!*& %.o@gC/B׀sx[ \kP**M `󰷛Mm}_7ۇ&ܳ "7y֡oc <vudj%U&FQ_ p+p }1VL8S|n w cr&%)Qhhֿ\Z(:k?szeٵ6Wڜ_i>WzuzUv>v^LwVwUs!Na0Cc[0b|  Fp<"(j2Mz7cI"CX:%no@k?6wvD'AAn"FAW#* jEC_~,Y `-Qc6uCTDuwX"kW1w$Ib/,1gRcŲk\xP>'0ƨ͘+@b ?|B?A,WgZ^h}vBK.^^^,ۣ*SԴRz:<7& lWCY,|.` T;`= xJݿAȻ:K&ְsСDTUQk`}h,RO@Lr`41 A,²T(ZT +%bQ((Zp h5v_vtOˡ,&v5ngvgiZz6Z[-4 DaLB pl@O1) K'fs,=9@j"M}!(~ "vfyX.NuKh7tT23T5袂|Bk+ =T hZeI 듢+wƒQۋ?a>hI, ;޵렷 ŵ1* yovP &`&.vV:V;bj]E FaY(JIJX BBrqفlA@Y(, ba:St X!DnOv#ڴ>l-=l{ͦivv;KѾT7D@{k,^Y:n T<0pcHRCyF1_|F؇[~L( SU d)Y9nϵ{YPvXRjyL2[6'JP/O@WK5tVP) 3%.~<`w~Gzǩels7"`@+RW4{'k-/q+S߲RX(4]B].ZTOW %bU pDZ@ (:wm_h,fcacæ ?X{iفM 6>l-m,jº<%C%` B7ðp ?Ff;RT2iR 5.8ұLj-~U-݌~m.^UW+U>r_@Mwv땸wM){=cв,}tu7@nȇHEw*qN~_)wfzc6;ngBm'(Zw7>Xjj)C'" aQj^iS)_hszb(AiCpXՉZ{Ǿ^-\^C;I=J鰿`>tL@\!~ȻaJz甿+@@XHkvBQN@_Tߕtvgjݶ͝/ͫ}F?; +F}~Z3O>;$PKRRaQv|~AM껸k? Ћ YKM joP@ݷ6n޾}} nn{Pۇnvv`M.l"kO-@>>%!n>3IT? 8?'8je\:i-Ks}ֈJՙҧZE?Jsg~GA~J^=Tp@P):g郯_+J0w8pX%ʛax~Y.{u̘/춆V4o-_m @KNVOZ}vjQFuvZ'+NZJ}EԢD*eX;% @5{Dn7e/uQ;M]&l/`݃wkwm컛ھh0}m7ێUc? wqGRcVhI qJI̴3@cƟ/s1m; )Z_xUQ^~|Be m=B6ttazoCAS\v G}h;^&[׃zӱQSc_Q*~؃rHqXjI v[,}k~i}\TB8+E/ /. }}QB_Tz@, рFS:1GHΧޜ0Ϲcw A@WT!L&ke `_Iacڛƾ}4___~տ5ۛ?4v׺ :+z,#>;lCIcW4. lBϙh#Տ(Pj c^~S/6xj XA}Hݮt[+qp?A \<V }|Íu>GcgA VK{+ w@I;ʿP;nǀC7ipk{ LHke^c޿vOToWZ,:=1W<3ETY*,Zs8cGa0D,^Ãp2jTi꺝D?X{sgyn޼ooͯ-h~}k޾7nj{s45=ln[@Lc@ |CEXpp};87@86!6&sw]\|@fq/w3) )y:'J#JL/W8D#;cW7x 0 %@ U5H5H煾,B__PgF_}zJJBjge;?@o'"1OCiO+ ]P]kJjZ:=UB뇗}x o57混v/ouk޼k]cmhs A9 ! L]Qc1%ܺ"ȧHL9 <ӏY \#dog4^UY\0tYu;)pVWKSF<%,Y:/wwWbd һ@|^aZ  pAapz kc;7h +Ei^(ͫR_Te;{ @iD'ҁ-(~?zc?$y>dP$v4&Rmm< D[Zݾ}/M~ֿ5okYU #8r9i^8a{BeA0yk炀x|n` !)\61˗F)ρ @c4eeeӼl[H?!{4/ʼa~@g ` FDzA;`o;{4y8T``vwn5DC:ҵ+O4WPi@\*U\*]}i 5\_W:?)FOᾃO7upLAsvtS@Lªju>}Mp{vf\ _\拗eU_vϿ_7m5~ں)F [AAiK)~GBX)0lf%s,o{^X}òVjJhjVdkCdݺV4eyi~Vh1{}ΕJk?]| ^S@oa@.8P׳=R蘕+x8ihcFaн0`"4 (Ը(:]| Tj\/:YVUv`oͳ:?BO~>yG waxXj'2@iP@OO*[w7_wO?og[??ooaCђH{G?)RB9 q+Wx)͒/ yxcJ94)$=O# mYiRj @)4_y`i^ky{ WCZ"ʿ-#<ߧ{ _Nc4 ֿkahP7P!*, h*@ J4j(o;SL؈OXI xϠ0ޓ=xZ!hXVB8꺀ݮu47?_k/ u6_}4~þkmc,tWTα\KR3;t)$n+}x9? (Ը\X]T ˅QB+cqҍ⇓~I W6yx8pyg`?wuE}-h p|w`ςZZ=\n$M "K2PJ\a+U\WV˪۳ [pP 0Rޔ 8K?%Q%[H}h'Oc.)z xO zOs+~8_;Eށo`CY@ P@`)X VP*X@P~;v6g5-W] \'V)?_$q{?M-+RvŋReN ]UZHPu>ç5%eRc*҄uIp|Rv}7ײ?]V8zyQ!*uUi`gy%  :+!}mP:>랷@m@ܲ?Z;,`oh( Н74PQGuc־Y1'Vzk .D @$ "{ih~8iQ)Zju6S'kVKG PkܓDwP$!),$Ǣ\)c $/-vL/jTI%Fa\Bm4jPa1: ,[{Se ~8i3_݅?msp _ZY+,`, J(U1;P{x{(x"?xRWOA؁ü_qj:sJF”PNp)@&@- WKN }yV˳R_}64m%g(|q$1_ aցR+NN55E[B,*h JcQh{+EE/ ~>8 O?^Wz#x*o| B( aIVa PKXaPb ШAcw#&8K"~}q|L?ѡRr9@;p@( BU*\8+ya.K}yVꓕѦPmOR;'n?8>2Ds 2bx)&Ҡp,}@>e-zȜ6(c@!6 AԅƲҪ(4*C8 ?d^ }|@+>?zey"x1~5S K=mV[5d* `:ſϏ/a,RP`gw6gXX&?%F q8`$>| ӸげD$:FU5baJjrղX 5Ѵn;@n1   Fދ3:q@>U{XSQ c]a\)FOjCXlCv)4ZVxS>(h3 )Ŕc$XRC; [+[j(5Bha @@TI7 -~ɂ_`R pi!l-~'gpG?KnjvOdG*KPF'hDp7P e@׏[pXYұ6|c8V)3>}9/&ϝ0XO>?+IV Uqߺܱb~spwMK0M7LyNWqLVp(qH`%1%ljBT]ЮeJwJ R#H}%8oh<`z|ǃ?}W|TSvD=,mtTY+T<1u+m` hZ_iPh@Ag{VL?ؤM!LRxdꔿ a19Ya`[_5PVZUªh?XhUvRQoe;(\Ps(B|ca!pTbb<5Apj=R _:Klm6DZ#ƢPBM7P10 8@ x81L(@ tqƛY_7dSEKp  V@ JU p<$?7 H醰i}+vJE[1bщ$A}~,b;A0E|Pg>Y5B]kS0l x2p `$Y1@I/c,sXXq-YmP  Z M?n uۖ1)U=}~nPVVᝡ>xiGt?'_7ҭ݇-Oio3wd7D;h JRdJKx jKc j~oQ 9_g 3)>6 BqE.oM{c`p_#fC?4|\YDeJRj]1pqZTDc{kw[pwz7F"7BCR3!׾(ϕ)r,_'D^2h1 mwIP\XX0Vl# |ACfk\@5BaUeTY*, EQ[6@ @*hSa\2[24@!,g;/)| QJ B!D@MMP[w B+hP++N&8TgN(zu5ӛ#px  Bb?{@c f_k=_'[O7۷hF X¼(Qg+. 3 FO7S$bg ^4)pciL^V>ז !VزsRQ'F,*"@k{KK#ň eH1} y.PnIDK)? =豙`-n]cKd Ln@ Q)ծi*>ǧ_^s_k6$ ]gk~)nn<6s0lC4DnKpK-57o޽6G*++cEaԋQ/Oߑ{O[ܼ -Z ƋחFRFDU*oaQg)~ʚGlB\3d> cP6vi^.Yn֥*XTJZ-Zyڎ bcŅ `kwa2gpFM p珃e2<,oQ1/ŀ@Zݶw[K%k;n PKt7h5ՄЫ;v @v@x(wWpYڧfx< ./9}g6d_c7<܀ _|1_BJcU!Qj~u.㜋J*??nx,wνaޥOlvwJ(P-/.. ӵ'k (h}M5aPs 691PK!5@f0M$ cs؀at框RS5mZhQi:͆w 0~VVm݌EW-(ݏ5 u} &JlCP[[oyB͛ݯh (8]i|.B_յgJBtB?Gx$jc6AK2%mP{I?``pw>b :=f "jXRh-ֈoꇇbpN s3UG1cؠ=kV̓;riRkc &Dgc@NGNmje*@Pi}j}Ht&ä>7*L7Nthpa@a ~v'ռ~K}{g?4_'7hF/Jo?ş7_ť֧'anMdכwn|Gi >NK#=Q(1CZ^h'``_dt@/o;0h~ivD(*.+B)Z Y7K=b0LwQf$ B|=x623BXc?>U,׮QDm7v~oᾶzOP,"Qkr=Zy_; >a=ɳts?&?ٯcDyKͻ_mg[m޿"ULgwe?_igV/>Z0X11~1vbRBV GKp\i|rx~BL6q*Hꢐwg&0D¨bQjU Zj,mcKn9Lt@C 5ЯshXϚx w)ԃR|a_R@`R@n jZF;J/FU R֪Ĺs#PS ˡB#8?OuO5 gۮ}˄l7d[w϶gSK߽'- gF}4(~,Ƙ/8CZ"@w>ٻZ1ImLMĴNyqiqx6fsؓR/ׅ$hTݒ7D*v?q=v.[Cmp_j#vir_۶걷yoi7o?_}cnm,u۬: N4?|[*[c/@W DLN ։JSRYS$RIJaJ"u|%ad݁ENWiT7[4{pj] Y@`_>vv{Kuӗ~͐Iw ؄? [ʀѤ)t| @MY\K<5ɏTS#InIiB!hkw~}۽vݶQ YD]R #>x50`}Iz@`f`C> =܁i>}[{?/~no{wc;kB٩V__XEoLZ5`UwT-׳95{( Ñ29L|hF1|K^RYnuNN:@Dm{hQOn* VXɪku}m¥cHA<\| %X _M@C(>ș5UVRU|O/lgZb Z ڵ 8_)aW5@d چl|-vmn޼kwusv߼o޻ɍҡ=PxzbԫBMeϋ\U|̋KP%aYjE][B?QAHfbp}El%oua".r|]8 F5cҌ lr:4P, _\pMCݷSlfmfېCeA2[9&!p8[~_Hl)7䇊)~c*%,H /f~zLwj3^S[nW;,k[- S6ϷNoyק^Tj.biTYi,JQh Zc"::WDjgZKMc6 յzߴĦw;}*{{nܾۇ ŁxBue~*~,ʘרO%r=;alg(]6d*ҵ'*G~*w?_P@տpa)Jh? ]Unak uS7!|cW*'il O7pX- 7F= G%hR4ڗ?7 _/1V@ i&8g#jnWξ{s,֥Z. Xj,p,T0Z,*RcQje Zc c;G 5uCuPiimn[vS桶e- <4vsviBE(CY|1WOVeI5BXCaic;y a"(sYbei T~N3>vf*mM]um[zi?c-iYsB\T/Nei]UϋoL, r߻qx0z9yE8}WByQX6S@NH Vw|w'{/޹ΠrZB, ڻ 6֖3B(#wNls1ֿ@>,AR;\RtU @Gb DH/}fv=R`t eU`UX- UV-Ptq(4ZҪ vʌ}ϒmlnkִk?5Xo=NATpun_ToOߔ7_g8ڔ?zN_FR& G=G}Z^(vB00 '? iDT u{h쾶mz!7<|=d>Dʿ 4 PwZC<s<~ڐ旔bC;e@@CqSxҧ'kaoT<@mK;_#*~&KhtL@gR ,uCMmn,؆Ph˭5jW۟~.WWNA-*BOwX2/ݑPD7nO&x:݀~ȣqxa>Idž$:Wa0Vzei*"Mcoj4S҃oH|li`#ysTy,ga,s>m.@8IF7~Y!]*}nSn_F-3^^>4W:;n'IBm /Oߕŗ/9?E *_wj$_ǥ;!I\ܾ&) LL S M'N n>dի WG߅_P?1\klW{kiqC ;WC| p+JsP s ta۴w̉_G?$Ojq=ŋeze2]*/_syj UJ9?ǵ&'/ .};&x$ʾīnnۍ(,Ս}m+h-Wץw]U~JW h@+vwc+oIO?'sF~0Nyfl;S(+Mag?NsaʕnkMФSYXI,ia|t'97HJ=㻡Q]j_L>D=zZ*~(ò_ja~?\!@kgDϰOqiQ8=Y>_aزc#T d&P);xj3XZbZ) mwn?R)nFJՋ cV"D@N1R☍+?_VL#uZ=7{Mc7[K=vgn ǾǏȁS/saK>ϱ!:0v O:vʝfL*We]EEqyj 0zH]ecc3W# g 'S`>  wر9 `U>K܌kp; OkP7@ѾzXfP쪑8*O1Yće I =pR F,>hTxvbE_?.ʗWZ`>`_?@ =1;($Xiܘ? |_rU0 #.'IsAHK|\"h>MCP7@uCtn6 >]95b |N ϢHovߔ)V>%"piST71u  wVK׋//|(秨,[v0*~*̓Z:|t8NC_^  8z?._20_`vt+Bl X[7S[3i^~Jg'ȳ7O[NK R?DAH{q/̇p ?E%\n1J axR شAX&{IxMzXj?e3JsD'AA{>Bgr3T]8I2`F cۈ@l>l,mw75kS =%?uEԱA$?yyN;OJoLT& ʟ2!ȕ6 *׏?,ʯ_syrh oO!co @)L@ec{)x,9FkԲΙ/CA;9|csވo?/ ssS|*Ra\5 A ]cĄ\$}5cZv~ 8G!3.gJ)&1?[RO!#zhS$o(Y*L7[?Mʉ ,XrY{%e18O+?S:ϴev01{ k21!&oXnR5b?4fga؇%ap牊U+ݭ0a< i_1?plB Zn67a:@lcIΆ?_]K˳;nN\ NyPH&@K4/پElzիR˲dXJ8(EX::PJODԓü=% O1#P/br޽V#?8J]|] > @۝7{=oNsc\E9@ox|sNRVy@:9XC䧛Zs,c|eR{rcVgueeW sv,[_vŸxڅnbӷn^_|/(tv{TZZ#AZ./Mv@)c׳W$' /Bd "nvd߼7{YDA嵎dץ4)0Hޥ As/$rbJ_kq9//զ <5o~hxvjOo-Wo7Msʟ@w\'&>u?wxipK¸E%t]aa:=y 1S0DbcWR lv޾nwv}=@Sbʨa3?KyN@:ȵy5cT}J(\?Ÿ|(ja~vYmU|gZUcrk{A`O٦V}6!j{N-c9@L x?_Ǹup28Bn:F۝kXöqKR6iX` 1t^ONrPHO¹ER\C/w0S}-*zU~,]U~EQd(4RKK+gaأ/=:(0QYa= RY1?]Ћi9H)- HW(rѐu vs }Mlw`jfci *LeR;O^#SVTֱaJ<I/ņheHR@T Qxvj_-Z_0Leh4"x;,w83JI8: D3YO?pBO(Hvyg@F'Ny3y̷c@=p{_뛻u;p<pTr#\nԘ0Ӟ?(tɢ[Gtc+Y) 4/r7@&L(a^1Q>~(Pȋm j!>4ofoi'cAWScSˀOO5fb̅Is X Y9u9yJ5WC$>?ɚ>םzeRE7U cݮ8T)))T⤮[68_isa,HoY1\)1B`'ig|Ys09J`&[BtD.I>$%>p灀v>Z mD]m߼7~EW d.&Y)2w?ccRk0~T2qxjԝR9sr_U=eqN2)P9(|qYY?,,xUN?זXz ƚ\0ZߞU/O7M`Ҙ*C_ H='94@a'zwS_ZKXjB qЩeuSvN7xJ?eǔBL8} J/KW_[_*Յ%(i4m|~׿^HhZJKz !l&tI 1vT[}"=@!)kj 3Q)$¹&t9`@"¾&zwS77m,$/[嘸zʐ *!3ùQ=|((c)S"s@þGA<]/-WƜA-J@g]B뿻d0-uDخ.Cۤkr.Krc> Ӱ3ҧaqy *2r0 \f! "@kw7Mݾ{hno-Prܚ4&9azb 22҅hU/<bCc{8]],΅I+_1@C ^:%3)H.ύ:7ӕ² T]H0Y;5GF !ܣL_!CW>1O#ݍ:[./͕vEy p^Pv:{~1 >,|6OIk.m~öJӵR|4_̛fkhml"`" _ 5ݯ?6 '%y@@a][1x@s@3ee k`JoºHh~ZJ]s־#QIDAT TEA~/)D~G^5T9lz8~vLgad$.Y ]&hoY\!/nbȫ ɑ>Q"a f-|ʸnAɻ7m8*tԫeoow|!_ LKA2Lh9| ar䡁t9i0%BX /."sy͝rey]?}(~*|YuϭA Y,2UQݱfpPH#=^8Qc\~~ĕ( >,]+UUƴ2*7-AXtH, Az %  NKBh e5- tCcQ^jŋj^^u ,QAŹ65Lc| ęS_Z;H mS>?L?\:JJ?@B!RT _>,OzyUsKTԪ].wOrȉyĤk1vyA㵁Sq#@uq*x+ޣXr 2 uDJ9>h"р+^^Ei^_Ww_tSp8' ;p2 ``8W3 )~1)~09J?F ]mkԋK/ϵZ-TOfx(bPS#)#lH=FMTa9A('?pN\)}[i 0Ҵ'`Z_4juyfZB?|w 3&5iȚ>! 'o1OxR9?HX颒U= PPV$P6*R8"uD.)>R t|Ţfۨ;l1T8z߂QS@ I*}_f0ȣ/7s@j&r?HvgzB;D @avQgkNJUKBR|xa+S~ŬBQ+qoxV%Bޔްʂ$pudoFJV*wR?o_X٪^EG:4,Fz0_ ]69( ]'%ꓔ4zsCS%ҩ B*bB@bBq>(nciN:Y)n~%p8v]4СLWFLs}JVɊEz:y/6Sخ Hx?0 Wa_I%w=}gdmIi|ewc&NHhnL/_/Jsu^蓕Q۽MC` ā8(H3}1{w"}ia {n ]d6IpEP M<3tղRXFw;A;Ow~?`“? )Xa8<=[&FH=a (\>`,1,~c8 %.20LK\"QziԼdAQDoJ,ץyuYꋳRmvV?vg)|C 9- 䔽 cA 4R7 uN~xN82e_yL2O,+'F]uҪ* pP85<œ{$eaıyy[ Z,v?_hsv+O˰&/xeQЧ>P~:H c"X$$LH aTxy Ea^]eij[YW4XXS;LdsA psnx %C |(cgcՙ[ aQi{EbPFRӼ!#)_~]x 5*E` p_욟~{ɂ8m5al>t<qyU<1))Lp>  N:;j@4 (d[ϓ-0Xqø*^^H f..(cF!|#@Ԫ = ]$C:%zkd|*M}Ҫ(nS*~Z r``2 1Hb0-. Ws'P8uSUPR'JhPkzKoOՒVpVUg  y+Ɩt3҆(A{*3 >,"F]y  x|3́m,1.l=ZP+e/^ūrvRek7hK / y̰e%̳kp`@S)O~qcsJ 1*WJ=HϰƼp{'!T%nsŚ3ҩRNZ\[K]@1DG?k;H+Yݜg!\gKa<AjF TWyyUœM-@oz΀ǁbsbG/3x:Y7bNy5H?GsvcxIy[0 BJz*(sIs `K9)68֚(ֳ ~W_~,t@AN]Je~ KcUPn,~! #G43/ Wgm}4?qo>17@BOFfppU XSצp?GiR剢5bQ(*RB(vfr"Xs'!f0>߫RNAS<00ؽzۿf:<%.9Rm"Xc 8Y )1*ؼ˅@!1 J3__W_}=~蘊U9PF d"L%-)M[~<úy_}}h\,mR3F@iW TƼj?|~ZҨq+uL٧vDq*ǘdfN$+=$gJ\Z'OndZ6v #~v]aݸfum~y/A0| h`1h"-/҉£~FxBD,$ =®gDB~yU/^J+-V9?'m1Gfc_۔HWYD^ @Dk`4rzqPXFc? zO2GrvM҅.>wG&=5ai^ҪxH !q>!1>3_^0kVZ4~1*8_#o"!FtpeaƣxNJ(eqԸFG?5EUgJxE6M*~6PPWe_Vwn}}y;002AֈXˢqqH_#&sϹHZnm6^J_]s}QRn%,pO\0!@@X'nLeN8@B|JoplI?F0-F#vKբXD?j8eTo cNC0A|*p^'ra=‹z@'kf<fx+ o~@ڹ"bw0WP͔B Z__ԿނFN#00=F}2>$~F8E~#eD~upl(/41 Hw WVdx " ⣊&Tp 3E߿.`UW1PpʦXX`vk?+nI JB*yW Baa 70ρ A|xTm}^.ޕ3-,}W5v/إVh^F: @qU<3Ui~2?k+R")H=Q ~\R|Lk0cH(7UhD*]߿T-g(6'N[bi{"W. q){~F ?e~9 99PL ] )? Da DWWyy7'+vh  /%]Lq O-3'/1N[J&]"D,A0[RI*b(/~`ʞrI=1BaxO6buW,%(k:~ L xPn<Rm DPV/.J'F-{)~ꊀD)25$5ĜԷ;WCRTl{~,JIogL{ ")!m51)|Z?8qX>'8F"US"HOmquݘK C _ЊJ`H+` #1[Jc _Ң)f?V5BaZ[βr#T]~ 4#%Pyi9SGS^$uߏ]&;.zB_a|8(\.ʟ)R !JΌ<5tmԻ \\?e3$x )6z#]orݕjҴ+ǰ޺w^ب9ULXmIʞwgm[ZLxKh?q[P#\7Ϡr,埊~T¼n..J/ }uVwMS7DNr0,e04ꟋO% ̦yL|vN-}}ݟVf?}w\\)xe3a[ȌyzBH"z$;XmJȠ:pv]  CE攴?> 4z🝈8K3/A),+Tk s}QCm7KSc` /~lL)̓g %p13:@(L] ,x.ّ9z+]|x ,*1E,P!Z!\cc"J~Kp91VG졢+KKLsp\d6 !($@`@4J@#m0ޒI?gE@``v|G>09<;“KM51[)yn0Tk* cD~ᣴ^܄5ͺB-fp'D)tw2sRi\WNOkkCwAU >/Ver)J?T)_:a>)`f>fzxsX 3er"jDl9tR ؉h9@`Ҏ؈` PF6Yu$+_~lhuA?b7@A'bݘq*re0? y;%lYwIeNWF-*EK])1_ <@ `f>nQMi9?7$0,n㿠7KUMXOay\|?_ s bSos45a*?)IVZs2 |( yƴ!d9M53֍N|*ݣ](c˔\R;s+.a8 vw9m&&^v6{ q.ޗSDKK2ˊG~p8`iVӕ֗F_Ml݂H'Eǥ16 UFovQsXy`,;ƈ0>)K;nAL?sڛ;' F.iRO)ׯ5}t'\W"1m e&-PHP~OB_teјЇqR&@9̀p-OPM- /#}\?o_/4\S׉Q+/р ((ɥS᱂vX}?Lf'{l0kH>UXσvkyƂc9wAJ8Zݷ~zaKaD$r1>Iۻ,bed*w^d0+&nى֗gF q˅( KȽ/ >?>X1K>u`Gfp(TN<)8), ^ 2/W/- C_WR˙iPj|wDDSK)q5aq4v85tբj? 1/^+) >p`!'f&SyJP")8AcK`-@ RH"@ $nD$ Y? ]+H˺buv!? uaF@}uesu'uT7ƅGXU@ @LԴAZ?=SvֈpHh6:?lrUQ(7KN;"9V)I ))6ϑ0xri `[ڬSuNH0<΁cÐ4,Mxol )@J G(̰>I ô\Ybie´ ;}*  N<]k}jjQjeCc?:` f`Ib[XQX֠7h&kǢ,AchkL|ZWA-~2\#Q:LXZ=2u *?12q)˥n!aWD /Jg;np\ T'KOZZmv  9TcRG LyX~[Zh}MPi;مa' (Q >g`mqI]kf3eJRGK_"w{`Ь< .IHhƘ|K9$DSAV\UJZmo_inOOup 7Ax]͋m]ev|TSS.S YKT7h_(:1/ G2c|̩kgAKS_ H+s&Јrʬ3R'#WD⏁!݈ irc,C%ͨ !;B &AJTR+NWF-JJe\0R Edf>mQOfMң?k h_Y#n[]8k!hR Xx|r*`0hc|k=[ſ+n_D?6r PH+mˤo0, NF ]F!6A6ϡcʧqR3`6Sa4RJOC>$YlIqs@¤ʷ eȺ*zg T)y:ghVfXp~6$ Cmt$Ae "(u_2NxU0Ԋ{MqR; y`^LiWm ēe EbrS| ЯObef>~-0MϽNKӹ`5i1 ADF` P >yQVrOM/aH6M U[1}h'}R0/ls8?@t Pݮ'+RZ'6O+#G'30K(u9~X֖hZH;e92tqvhHx:;pҡC `b]+Yr~TL &Bdk S1^\xU(P$MIc7 A DMbi 'K EXD!XxX ,p \RIKBFj&j-mvn5Qc vu9$wRvr/2;G8 77 +SaF&IMqmw} 7rM ]֌cJ $%LUwPVVJ@nJ4w,՟.Y$IYұcrF,M7(, C%{$g 򥌘H ŋؐtUg(l*fK'Jԋrc(Pe$ї6=p$eKJt.M&)nMze-=l{hC7a{<2.K]Y~cG\ژ4>xeBOceyPO陶 hsW:@~A VUѨ #ppP`q=S~mZ9a$ h6[i)˼oWao5/w>)_ D t}O7 W~<w 9w-.omhH} 0E,Z!pQ*\VZ-*Qtp?)Δ _W˄= jFQ,<zw[w7Xݧmszֶ}s7 48@lw*($ Iܵ>S"W0Up "])v6pT)7Fg)nO%ʟ"Xv@~H^XRWwZe+38e(+WI#SR@d 6[Knkw퉚HOw?,п L^҂NtR c$uO2",L980^(KT:prt p;wm0":+{/T0FR٫Sy+3? >fy,R)xK>-R %,׻H.Yǣޘ0굄q\_Sp`)-OĔ!6 ˵)r.{9N9w-ݎTN"H) ПK !W @< ҧX!H" GZnԝV*;d\WRA nV06+t+ע,{;EKsb^歀?]8KKB9OmYm'->{{؇uMrel);LRL$w3yçt#6=x4sWK`[{e 3|Y\4? g7 Q!`> 03pֱ G!}.:9yy۞@XxZmc6t{oi@Jo[AnD %+-J0i k,)/b\2|R)`/1i sɂ'%mM$,a*JݳhK*m}hLs`ȅ"0$ C +kV6ID-r Q^S_<  )x?z]$0bYyBlFѸVr+4Ŵ*%)MG EDEͷ0?V۟|(("$;6ߗ3bRdhc-5 fOA ,N("l#ԥ$-gK}zno/[밀pWe}2D 7ՃSR~ 16BS]#VLkfk"bek:zAW9'L׈!FӮOaXG,$ 4I# uOX9ηVmjwm WCɿ;F^ڿq3mS2[GkV` tŔ>Fx])S :0rر2+BpC* 3RQ/xicaz,;0td4s`1~pʃX IcƟj-PԣP5]o?z 1I)B3H6C`-'hIJiՕG K Q @Xʕ\/X vI T $UD(@AVhg0@Lh=З[Z؟Wڏ^"fîOప/=ŁZgB@HJ!/~TT^ @^o1+1rY@Bxl~@5(!cxblOǚ#m@%u4|nǷUp`: d`x{քϑJ5[CvkŒ'hþn+u[fc,;?``@JS@x%T5oZDq(@xrVBj)zhe? ]/c z$1(lКQ?1T Nd׭G}ӷ9Xџ (,Je@)XBy B;"/v7qǪFt5JKf?/tꅈjfԎ<ކP,C~Ӗ=0[B]ݟ'nvL.x}v)*1Ʃv/tqb_fd ˄dҵB*ҩ|&_(<}|iۇ_ UašLJc $Z<,Kg@P IY N@4G:]&ytRje%Xs٦ߡa b@a W#`=[deB$=?x)MB˗ʄ?mBþ߬ƘUCR(A$O>I`4Tl8Jc+h=f{} THYʴñ$l(E?ɟ\nJ'ɥ;?g\)I6V_Cb 8-!-yE@? Wuq~]~|]w_ (8eԂI7Ӯcϱ}˻-ljmMsQIgZU8 Z+@~<"xD1SyA,%u '>-ܕCWwp])[b4Ӛ',@`;[ILLiXo"YˤYV_'l†Tf 2pb dxN?_ GpAx1/#h> +L8᧻xXXUk9>I hH|ԛ2Ő_U?ZP-decLź&ẁz{` }* Hy\h]y # )JKzO,LĴM Ԅ)%+jR,Q]>\qگ_-ĭ0ͣq4-vcĘe]1 DHzBߗO*k In/4D+tY<|?fbfiLQG!:~{@/%Si$Nļg,|{H2ҏK/}5bXxs_Wx[qc0!L8X~?`#rqHˇ/HSR'WSZk56uY@ԕ:Divj~ٟva/hx`{iǀ{gdELSi<=EObB})_m>gl6٢@}C]4}.YGj"ހIүaD9Y%`fp#έ:ؕ|?tQ@o17ߑ0O I]E YxHMMMCįi JxЩd,aoWwkC/ύ)®V G@w'$DytۥzID@~?i}@2L {;):LGk(@8Al6;U,E! /1! Ki8ִx5`H rq_z5vn+wuW7 s~fY`B,m "hO';K!C1&~JiZߖAA!dx?dۉ@q:Ԩ |վ_ڭ! B zwHd1@ [BRzˀ1F8\G?<ǦA "TENCG{,>@m^ DKOH$ ?3eH/%Ke&e%u΄V^ג)Xr*>S/8f~"ukx?2e,һ-Y#[nH!@~C@jL& /R{gqwlm*ݧmΗkX0NZ@U v}&姺>`Y {QO  *`b`zj "TJ'tqN5dK@t/kD&&/{.%-q+ ~6d WPOzc9/z) n>Owh)Cv{W 3cE@ @J4ni 6,-SE{@hhE1"zP8aʁx48`%;oc>-LE!8h<`c屬o|M#CG(Lw@R%,T%ނKt^>?<+`Pojto*wu[slcjU@Qv-O84Uܹ\tɖ?R $;H)73]uFTLh7oSΨ)1LM1! nPz_VDldHBDK]/)K9Rb>f p?S?]u8s^\N7ku;/?4S-Zڟ?4K^(mE T]x |Y y2J'Hu%#^,k,'kG@h? DOӔ!̀9IJBܗ; @e Re]<24d F=Ki>dBr ENcr~;^޿Dc06ťrHj iG3C98H䤮HeJ{:~ܚf7"PGG5vij"XsMaɐoM&C@4ӥM?VcjH *ǡw(K߁v<[d#fL?FJ& #kt?7ʀo^1Vkb/>pC Ҹ6t Wjz^SJ 5SOK5L IHf))XI2l"t`* O"{>beH?  €upKe] / #+?ݏyq*Ra! _/boS3 50 #u]޾^UalQX3XfKF SjC5M T R3C":?TC ʸd~]ïA#@0f"3ߝ%`|JcL@<@U#m`Y{lF)i$_"# / ! Mˏ~tf-6; KX9rJu޽Yۋ3k-Lݝqȗ82nkҫ!D r(4.|]jqJd5qʲϯ Zb{yTPd5A&pD4 86a& 'x i xPGwU8Sy#ǎ5%O5)~)>_/cT I#C>_?ޮ7+Oje6ewbh 8LuaUM%d46*]4lz4mO$ Ƅ^hïZ?vc}H";'1t?,Xֈ{u{X9AD%%$^[}:$,G2%xvܯW{9nFl5U˙hgv4S%]#}! Ceu*?kUDH?q"3I&3 m_4B(<Fӟ=?%*hc~{n{hܡX C%[loԉOD`I߼ZU+J㡹5 FQY9ּYFZ($sbGZ2&%X:je{'BvށIįk$KB90ʢ& >1(۝?> G!s@qOG88YNղCڻÄS&{@HjW(I#J?!Ą: AKPƂHR󈻣oJҽ(gƈZ0%#/ZB$N 9FeOdCR/LfU8 BD <:Տi4د#>_XoA<@]]3X:@\'F#ݐ"w݆J;TQ9! _bomzLKڔ.n'raH{q=]o?mEPO#yDi6=υ34(;9! я&z7*瑈Ah@ȯ7q,֮ 5v!!RY~MHXoLώ,|;juNQ?u6+I@hݦ}nS@=Ӡ9>t;% 0P6H!s?'Xv>yz%b%}'B:(\\dCRhDc@?7Z ~/< 0 {slf{l\ %^%/i)ڿ{O,|\5_fN4]@QUs޿^7?~^˕=c!O08M2㺮MgZ-NG{C<L@MNX^wRKukJyjIm* m`' ~|LO@R?ӗmI|,ؓwOG0 @h{ ε qwn[ۇoV,*B@nH~V=ki>?)AmL\o7@0͜3`2C B-gN XYw|љ "aY| +lmj@LO]]g#B@"Mt&@OڃVSG{48e /;qKq{,fHuĬ)ːo)/q2?ϖJ*kxtW>oe1E;kc|cwe8A{ҾEԲvي?|'}P?׫p]A q>}2N%8X@|,vR8otA YP7D 0ߙ=[qs{vj_w4B>%oJoK¹@pT:1HLjPvx{~:4ph޾Z\Zʱad~x^T0:8`<]a) !0>fXְT  IAsgd?+ 5V7@?c` h{Ua{v۸mߗWDŽƴ|̗*@NhھT?GIX2KRa'}L @0e~c߼{lo6lөF7F2ܢ $MC=Vj˳W}OKeQO5_`ж.F| $T ӲHΗ6|~cS[@c_޹뇺n{l\ٸÕ"A!  /ɤvB?N?,' i Z'hߗ08?6ǮyfmgYijGp0%;#exߥs#C8k`1зHYZhD!as_06p}^I73M^ G=-;NC0%z[xkOUs}|Fg4Tm?5&=;"Fȣe +# 1Ͳ-ϳl?n#r,yfvl]9+-kiKa iFُBjH=|P!bS"-=bZYbb?@Wͪ07|b]H0ǮHh7cUяkېkAC#]!nͶiuu/=67+ kV5Xs=[d Iy=2d4*_%I\E}G-#pOMۧCn^^]-6V3sѝY֥t E~kÖ#ɟ' 0S@(4"T^Q ZCkC]4z9c]T`fٿ'VO5 }j7ۺz(}qJ74 %"O9?fx &p,Җj<_5M^hǚ!t'i|4+z;|ww]V~_}.0Fһ%dH bu!iꘉi6/cڿ~%!C S'{X ^Ybm{}Vy5` 4ϴ~<~{#$`$ѵah^P8Qm[n~_V,0 B/4`_fZ~Ǽ?;gP!mz}~>} Jw#Uϭ R{%!DbKe[["YRy Kc!Ɂ1˳}jSlօ) 5%Wӄj 4HM%"y\ ϰI;>^?tќO'Br8p>R;5O9Gs?B }ٟ1}O4{ [ѻ?CP+EǠYCOn\ D۪/Gv|}KΣޤKfR?ZА-ݽn<ܗ~]Xûw?~w6Ʈ N iv+TuB.R}|VH'^hO+]G V5dQ3ka4/6M&QXC?fm o!);e7@ٿ?7l>]ffbBҝEȖ}&P. jY bu,) b;+#Ya^8+ڞo c- dUN4.}Ț4Ӯy}H~  HzT5#~?1Mt-Z$-^ݏX~haCbe$J*~hy, L4T@͏ͣ^Nfj5@PQ$5#ZpB_OՇC}]p w[8O텦ij H5KO>dELi&P.D)Jr٧}hrܿ6lօ5kզxuCJ~'%a `oaQ>sC'3-+] Z= Z>^nZF+Bdg΢ajBTצc:`kï#~}xo܇cӧ]LJ9t#9v9]h}m޿DOdzD^?/ҎjKt0 L1fi ˵e1F99 ecC`B~!BH /j0.^vVڋd|>iԯ?%PYGiT0`: PYG}` Xw>>T7O]}/]ݲ=Ϟ I eŧ2>d녉kꘔw%` 1 ÀZ+d{Өbm߿9Wv*캰P'm \@Pn6<_`'!@ZN@;ҫT"kSUKo3`.p4Տ/M@럹Y`fϧq?ui~'{gP5}__wͮtǺG ~Cc 2(u#d䇈4& L6q*,Z8E+kLa-¼XwϊcAi@^W!|uQH3SHW& _+J("K@JBnٟv$?qD>{4Bj[poeQ}"%"Z?/B4ŀN"rK<.|1yˍ}8[UQZc 2b?:+Zfھ&_қ e63?Cař&XI:";jtӠ>(wmB?(&0 ah*gи?כ}CͶ]Y;D@N̞\ϧ~gHKr6_#`F0ϟeםi?1W4[b>o0Yٱ}t;< d'y,}?V+:a-4@>p~_+IGȟ /qSagZ~hVmj~xW|P]o4);S_:_+|)gMYV"-8JiO4Y PԱEvzX xzc^MXYkƀ@F$&(dݖI#z {j,ar̈Ke6T6&h>,+z%M2]Pz0jv?ly_(L? ;יUt_tuW}mYڹ%͟j<2j"~Z֣PBg,|[HYP&DR4MJc0`y!Wys팀uQU[R(si.=im6iB@l蕨BZ͑&2-ߌ8J>7i>˓c!y0DOVP+#|?Z<WF p¡Fޕ}}]C}?9 LFT$ӿ~/C_odLоelX^]04O7ڷ ;I?ǂ_e!4?^uQUaa* YB#I3meCw{bo[t@=E"Q@LtKndZȯ55{p8 @2ǟ<:nצy?G])mz~V>Z؏_sHu@"wihԯglZcVj"k-,CeKc2iCaUXY*, syvv֠]i lC v*n*75dIU󈀑d) V@;M-1s+~ B?F~-kmٸOc}_Ww7Km;/c_SJV(IK1oLj>&q̩\)Iy#WleWEaZ;= ժD/SKDQ9i"! zN3N5JqW3Qfj'.$7+G: 'e5@7#Z_WB+TlvO7z[q^bMKl;+BTmY"<[o &iR`=1 ÂvkUC7+bc.6+Zum=3gM1$jL$j~LӦyA (/f~Rs,dF⟯?``^M0@NgQHYkt[n[] AF{_ EKhc <{אo!VT?\/v]Â4qZjxMa9[YkQ'v+ɱFLCzG&e%dKJ&- iwRbGm}ڞ~:H퉝:~FR?/:A CP?noZ?5,zBQO,!d6XdEf/ "~deT!ֶ%]?_VW]skx΄?@" |,|0}S-!y6pS>/1lK`ھ:ߘjeV֚P ]ZkAZw̐Uz|1RJdPt*,ۮBs )m*,|QIy;de?@үؘB+"(`U(,mVlmE@C0K[-360m;q~\VV$iôsde|To(PL'ޕpN/ЗdI8Oɟxh?[9mCm_W7nnk署ԯtI}:}~I/YbbG]{R ~ia N ,?&38U_Ƽ:0"MZgh/`e'AV6DұS?Oj\~K@a?)K!am)O4m~/ @1 huf_6>3PmF_cܯGJ$ vN4]*;9 L?[W!^oNf0nB _o׷]_Wշ+;$I_KlԿҿ/E>E^ EКH#CC:XHLZ Pʣ+zᘮmJ` ӗA׏>nq4sYZpޮʴn(VL8OI&b Ҋku+dYycwNqӲؕ.IVƕgAl%R ûc~u?ݱ}O#M,O!NS_ |I0lۧKdOS]"(p—^d EЄ6g>xҝG*x֚|Z`ln k[E\0{!ti5X(V 4  0h}ceydKiy],dHH3 K_`4^ Hi\bVUv{ĦPX ,(L_X<_ (R#y| ;Z!`rM!"l_gH xhT.~rw[rw[n*h_Kю%r-]7FK#Sl]=b&mɄsbuHT:6؅O<[(=iB X2EaaZuQytΠu e,!44-VC[M=8XٕY®V;'3D]CQ5g $ O'I;0RԆ[P t >7\+;:[DmfC2j섀nIޕdqXߗGwu6nnU%A4A@S"Sfj_0 4%=b4AtlǒEO /Xp,kC( k k "W.lmj.!FC)XQTx:&DJV P6zy>`uLL0X1q4d z*y[B$v{6:o;]wWms67ۖM)NybB_Ħ4TȠ0c(קin%Z~ RgS1& mCcپf@Y{#!XX+Y8;ΫBBӑRGPo}^7O5RT&OMD!fR~ 4f xM`n5]f[yw۲$d_C_>˭kbCd@UFJZ-GZ?[}mfO,BYl_,3n1 y7ǺƺP!][lX`lWe٭hhUxHu%(P T^gzh:J(ǹPQ Pv!"@Iu͟ݶ5^4">Z!N(@ ְovsW{8j_zxS,*O" ؗr|B|@?Xj Y kwIHv঍@k, zhMa Mqy25*wsw_+y];@s(u6Q.M}i>2?ᴮK:D$O;w3*s7t;fbb`cmW[F>ߴA}A?^7 =8P;c|Y{_9DM>%S-B)B$E"[2B0:$8T>rP]R~lm p%*簪a;eAݴk,0Y#:;( DŽh ٫f)iYJQOh\xNʎ_؟Q@ۄ߮Vk/iQ7=6]z/tu8@86oeVq3$O$]oH࿆]34_W,d DV1ʦ m qT@ jcؘ@h-Upwv?V>o7U5RiOjf~h_f 2N4HǚߧCSWa.D@C 0qUCl=ߴ֬W)5Z,Hw͕"}Z f4A u) Tp`mM> (4tmY{ n{mO' Ng uunI p`зUp+X{_67czn5? sP?o߯Yx,()tF@H&兄z엒D1>664E9ۈ_݇[Ͽ[wW߿o.|SZ佌RJ?0$9'P&^`y)>(DG5I^*H <2G<6i ?81~Dc~/(!Jf=vr^XcI$hDwʇ~bŢCs)W'd@F b9(ctVs- L))[vþc5 [3snYfznUgHf3`/?qK!Z!>|=05vL},3~; k#=4p}qW}lM(4e YhC}*-B5 ?j?mfuV&dr4; t`!PK?㤺="TMUXݱvww΋˳9[f2o^o [&ݭUOLc".毸$JwԬO]{bey=@>Cu&>>3.`4SwN$iyĪz>FS }|wI_-Sd @Xh2R.h!]jKJ^IuA`s sP =} U ® [4sH'eF=~HXxD['N5zK4z>Ϭ8~> Êͳm$ ?Qۇ"!Upwۺ?WͮqHQ#~S>SV }ϳjx@R,\.p N!'U:'u|Pɏ|<#^?vvGk ~߈TH&3foFeۮhlnvOPyb>%a *oU#2>' j_@ ZF=7h{'iz!KcB@6pkzl nvݱWm_={fww.7ۺ֬l}nGmX'e 960 i92%Q<\$ɩ*Q+ovfOGjm5^,dBY{<vx3>sDY)od @HSPR6[I<ɇΉ",{}U请{~믶{w?ݱc `fBB5iϭSӽXF~x c#݊ ]Mo7>wǽyL3,Vb/8竲fW:I%$wD`:j% b~I&# T3S jK;;ZͷvU p78~WXBރހALUafy%Yڄߧχ槫T ۑaR7u1BN[k3x<~o>տfWc|0 %B iZ )@j)l5>=f=W+5v־<`885 Uwtܻo__]طg\nV9PU붓ؖ FO{*IO?YCy3N1Gw~f0cDwpbA\W2]0`غ `Y*aU{tiiX /.iJ7KYx:Ӛ@C~EZ?%z)168S9%O0-3o"Tuwenq//^?{[ſ?/3{)``EoB8ng'@Zd:W%Ir/3{9=wD3M^h]LJ fPyVJ|H_%*W-j1aK 4ށ=xozmޜ^?{W⿿Wo_./웋3{YUaօ15P00kONniOT( NmAx8iRw==0OȞZ`'3mDOhnu : "UxKA,x1ƴ!?(<H _ۧZϷ|ݘk)us;FK(sC@ܓ1}T|4a4X u۲7k7^{Յ}wyaߞ7glc/6+s^UaE5`-CG=3a!PqJW L71R-1ӥy+%>`oHt W}caq޻s$bھ,0PXo_7G=#z[* ;_J^F12@27ƬE'jcؗŶ] b=u=Ի֜ٙ}{qiMÛ7?y]իK¾>?gksY65kkLa@al2ִ6rq"N$aP:)fNWpv?L" A/ҰUH{z1ma-4z3 6H *8ڙu=9wwP:_\!Mz5| kk}}AL˄ԤBZFHa# Be@{i~$>j*t!K ?riGWUxMce׻־po/̻ ^7}svf^mZzmks*f2V 0usrd3k{{0chw;d>4WqJJnOCO# K(J MyI.OەL}BTs hPW`{mݴ^YkVEk[0bb5z =_w `]̺u@a-klngRͿ:;|ߖZجVb6Źxyf]ٷk{yfEb$ QHo 0N% nG߂a{tӃkoUsñsgOOPEk Y6I} @SC"KuNMǡ"?1BAORp`]!! fX(NnM(Pofm Xkشo6ln1  /=Cp(_X 0g땹l3~wy^|~rc_nۋ}ufƚc-G, -Ւ?N傁 V~_ xo9 3p(~ho7el+Gm~[2?S4? E9C@2>@2!ˀVV8KB!bր#<&8E`|F7PXo;MhJep0שt0 .klc]ۿze|oݛ{gS؍_G.MU Lvq2͑ (?'S@X8V~U?}V͡n0`fO LYo?T ?f@2>R iK1OY&X?4Z-KN1? @uVvK1 "CaYu.g7onuCW7g5`H=]#9a [p@Ww{o_oW]}/]x4O L\ڞApYp `׽d ()jڳho|9KR8>#"~{nr ueGp,'6X7b5`m" ڌA8(g,;- P+nu}iz_n??mǧٖ56ޅ( V>}7e>[Y2,'m5UՈ$Y$ r~EBL躡w KʦujωT뇘onDx(K_n۲`ƚ60֘Ԟd>]>@L[AլPxGͱ귻ps̿dJ%W %.CA2RΘ ցh&4XR_&1}Ml-P =X⮬p_xt1*=C< o&Z/W@x,~W6uu_7?>?CSfiϔ$H}MO5diˌ?#Yc@i/q-85(L#ߘiVRK>/=bE@l-sP6u۲WME= YPC3 'M|~+~ ޯW/ }^?o\T7;6u>K}\炀$4/;W,d| 4ťZ+B̢TD8%XPN j)tSS.l ]Uv{?]Y2vzb;_~>" Ǐ\~ߛ}и ʪzUն?ޔç??Կ77;5 S,ZlG6RTk7,d|)ZBZZhJ)Ak ۔1?? .aKcprmg;@4xjU5˦ T!VcxCpv~}`o4L} }C??]W?M݇jLz6B@~~J\g,d|I<ڗN M@(R1BS,Kc|.|Y ӎ![wW9+0( @Ѯ(~[ ~YA/->_t?]mtW?S׻js۲lɿ.?E㗂R5eA@@24#fHSڶğa)!.%S-1~"Š/eNY Ob;PUp<o;cc P; 4PO 4uP*#s添CնO|W7^eM) r`,Ty!/ ~?p_F|Yvg!L_L%-*B%хnI ȤǨӱ}-m)}mikX7n_5++++|8TqW΋{Yًua kۯF#xpvUX;Wkw;tWpkxxhnݧ݃="?<=$@j~a[m_m_hџ2C@2 hBO_ُکBf ]"zV\B]ˮoHB'u)8$b@,4]#=༇c]77K ݫKܾ9W=_(V@hB: Yb1Ҿii=YKоԇ$&IB7k=C33#YxNHѬc-Kfd~} i9kc pTׄjr=SzY=x~Hi?$ R] @s$3A FO)дSFR/E K%e ,!uR%ϕ&|?Eo`@#H 釴TLq/!Us!?$4DjJ>UMqMyRC.3)~%E~K\ ܝ2d )-PsC).DE|KTAR +/RV+3HP*>׾'i{G" _*& v&^)u2IL.pZnUA_lL{tcd"dR,>_B%@3? \j~퇈1TzC{?`O@db%дz\/tN34%@$HLU'3;_-w kp%,4 %įhSCXd @hi)B@H'5GD\ȟ;@#%?wʯRBBHu#UpC4TQOe$??#%%l[@(T?-C ,KH~ (CDsR]K"1f% 3C%@%4gmЦOgd !@.-֫ ȟ%ҋ 1cZD>6Y-B)BcK韛ᵥ~ܯǐ@KD;Q5L=k>%@'K1OMY@a?'! ThY=xFK}O[㿂iT! ' /Z6$ h[v#7M B?P'.mVA VPy2  yJS n㤘234TJ6@2^2Zoheyy4nXɧń@D*_)p1_w/5?;u_ 20JZ_狀(a\!@COQhR ğb|OShs JB~1?'-[/u&@?XSh2,d|mhBy1K%^F"~dSM! ʂR ަP{m?ڢ"i1׈ɺ\1~O,d|X*PzDP= Y kz:_@H3 hfRsH>N%껴[}PSL1@,?_  @׊!D\ӎYR|kmu!?EO%T2H~Ⱥ 4W@HJ!).i?$H Hn/c--c!5#u)nL@s pf "K}?m2?uVmSBӤ+i!O ǂN K~0Ss21K!p qLH:Rc|qY =_6 }Q5 D!?EE#oBMHOMA4׀D6P_:H%bc@J .@zHhiK@} XjLxBd [FliRK"z472< ld?6/4P"bB/>&=F̉[R>_sK4τ؃$Tu(o-Ch:K#PĺH,!򏭙W )6?$p,! PHrHd [F 4~J))9 eHZ$} 4!4 _"HdR?i@l2YX!@_jH)wv !nlS4 D Ǵ!kLӿDYRL@] x"FZtB1:ryҾ لTa f}ZgiK}R_Be<TKB)0(K4S}H(">[*bB@,v ~J))€f}O~h}HZ! #Z喚iZ*! "ڲKg+9E_bI08H>i)@ Z\ K%Pz :N4k-Bާ b4kGL+YRCгd!@~!! U("=@FFh)睢%4)Bֶ%s B1m>lz4 !!? B‚t}̐8NB駚SKM걲vZR-q 16@0P: ! i"$,ѮR"Ce#,1K@?FL+E'p hLt"h18_K%% !֯)}Ii~=eX,@+ϓBd_k{)SS ! ?BHL@`?3G22#lK\W"])]2ߟkZ{NR?F!=E_ݯ3d #s )ROjcb~?E]sXy91)=%/!P21ǒ%JKzJ1@#>V/1V)B1g!@F_SO "~)Kb!  YRx[O1g 81PjKuC$=T )BYx.FBizL,dd|uaS,jK5E(8Ť*gBddd|I|%垺bd~ ygk@ Y &8#?)scug=I]KO]upKBBCF2225+߅䟱Cbe _݀G[c#$ጌ%+{I8|ƣ-#x__hd ###s.322222 ?мpIENDB`lemonade-sdk-lemonade-d88f5d9/docs/assets/marketplace.css000066400000000000000000000344711515430344400235440ustar00rootroot00000000000000/* Marketplace Styles - Professional Design */ /* === Theme Variables === */ :root { /* Grid layout constants */ --mp-card-min-width: 240px; --mp-grid-gap: 12px; --mp-grid-max-columns: 3; /* * Max grid width = just under what would fit (max-columns + 1) columns * Formula: (max-columns + 1) * card-min-width + max-columns * gap - 1px * For 3 columns: 4*240 + 3*12 - 1 = 995px * This ensures we can never fit more than 3 columns */ --mp-grid-max-width: calc( (var(--mp-grid-max-columns) + 1) * var(--mp-card-min-width) + var(--mp-grid-max-columns) * var(--mp-grid-gap) - 1px ); /* Light theme - matches website warm yellow/cream */ --mp-bg-page: #fffbe6; --mp-bg-card: #ffffff; --mp-text-primary: #1a1a1a; --mp-text-secondary: #666666; --mp-text-muted: #999999; --mp-border: rgba(0, 0, 0, 0.08); --mp-accent: #1a1a1a; --mp-accent-hover: #333333; --mp-accent-yellow: #ffe066; --mp-accent-yellow-dark: #ffd43b; --mp-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04); --mp-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.06); --mp-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.1); --mp-radius-sm: 8px; --mp-radius-md: 12px; --mp-radius-lg: 16px; } [data-theme="dark"] { /* Dark theme - matches desktop app */ --mp-bg-page: #000000; --mp-bg-card: #1a1a1a; --mp-text-primary: #ffffff; --mp-text-secondary: #cccccc; --mp-text-muted: #999999; --mp-border: #333333; --mp-accent: #ffffff; --mp-accent-hover: #cccccc; --mp-accent-yellow: #ffc832; --mp-accent-yellow-dark: #e6b800; --mp-shadow-sm: none; --mp-shadow-md: none; --mp-shadow-lg: none; } /* === Embedded Mode === */ /* Hide scrollbars in embedded mode to match app */ html.embedded, html.embedded body { scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE and Edge */ } html.embedded::-webkit-scrollbar, html.embedded body::-webkit-scrollbar { display: none; /* Chrome, Safari, Opera */ width: 0; height: 0; } body.embedded { background: var(--mp-bg-page); overflow-x: hidden; /* Prevent horizontal scrollbar */ overflow-y: scroll; /* Use fixed width calculation to prevent layout shift from scrollbar */ width: 100%; box-sizing: border-box; } body.embedded .marketplace-main { overflow: visible; } body.embedded .apps-grid { overflow: visible; /* Uses same max-width as base (inherited from .apps-grid) */ } body.embedded .navbar, body.embedded .footer-placeholder, body.embedded .site-footer { display: none !important; } body.embedded .marketplace-main { padding-top: 0; min-height: 100vh; /* Remove centering in embedded mode - use full width with padding instead */ max-width: none; margin: 0; width: 100%; box-sizing: border-box; } body.embedded .marketplace-hero { padding: 12px 0 12px; } [data-theme="dark"] #marketplace-body { background: #000000 !important; } [data-theme="dark"] #marketplace-body::before { display: none !important; } /* === Main Container === */ .marketplace-main { /* Grid max width (995px) + padding (48px) = 1043px */ max-width: calc(var(--mp-grid-max-width) + 48px); margin: 0 auto; padding: 0 24px 48px; min-height: 60vh; overflow-x: hidden; /* Prevent horizontal scroll */ box-sizing: border-box; } /* === Hero Section === */ .marketplace-hero { padding: 48px 0 48px; /* More spacing before content like news page */ } /* In embedded mode, use compact heading matching app section headers */ body.embedded .marketplace-hero { padding: 12px 0; text-align: left; border-bottom: none; background: #000000; } body.embedded .marketplace-title { font-size: 0.75rem; font-weight: 500; color: #666666; margin: 0; text-transform: uppercase; letter-spacing: 0.5px; } body.embedded .marketplace-subtitle { display: none; } /* Embedded mode: match app font family */ body.embedded { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Embedded mode: align controls with heading */ body.embedded .marketplace-controls { padding: 0; margin-bottom: 16px; } body.embedded .apps-grid { padding: 0; } /* === Controls === */ .marketplace-controls { display: flex; flex-direction: row; flex-wrap: wrap; /* Allow wrapping on narrow screens */ align-items: center; gap: 12px; margin: 0 0 24px; padding: 0; } /* Search */ .search-container { position: relative; width: 240px; max-width: 100%; flex-shrink: 1; min-width: 120px; } .search-input { width: 100%; padding: 8px 12px 8px 34px; font-size: 0.875rem; border: 1px solid var(--mp-border); border-radius: var(--mp-radius-sm); background: var(--mp-bg-card); color: var(--mp-text-primary); transition: border-color 0.15s ease, box-shadow 0.15s ease; outline: none; } .search-input::placeholder { color: var(--mp-text-muted); } .search-input:focus { border-color: var(--mp-accent); box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05); } [data-theme="dark"] .search-input { background: #1a1a1a; border-color: #333333; color: #ffffff; } [data-theme="dark"] .search-input::placeholder { color: #666666; } [data-theme="dark"] .search-input:focus { border-color: #555555; box-shadow: none; } /* Embedded mode: match model manager styling exactly */ body.embedded .search-input { padding: 8px 12px 8px 34px; background: #0a0a0a; border: 1px solid #1a1a1a; border-radius: 6px; color: #ffffff; font-size: 0.75rem; } body.embedded .search-input::placeholder { color: #666666; } body.embedded .search-input:focus { border-color: #333333; background: #0f0f0f; } body.embedded .search-input:hover { border-color: #2a2a2a; } body.embedded .search-icon { display: none; } body.embedded .search-input { padding-left: 12px; /* Remove left padding for icon */ } .search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); width: 14px; height: 14px; pointer-events: none; opacity: 0.4; color: var(--mp-text-muted); } [data-theme="dark"] .search-icon { color: #666666; } /* Category Filters */ .mp-filter-container { display: flex; flex-direction: row; flex-wrap: wrap; /* Wrap to new row when needed */ gap: 4px; flex: 1; justify-content: flex-start; min-width: 0; /* Allow shrinking below content size */ } .mp-filter-btn { padding: 6px 12px; font-size: 0.8125rem; font-weight: 450; border: none; border-radius: var(--mp-radius-sm); background: transparent; color: var(--mp-text-muted); cursor: pointer; transition: color 0.15s ease, background 0.15s ease; white-space: nowrap; width: auto; box-shadow: none; flex-shrink: 0; /* Prevent buttons from shrinking */ } .mp-filter-btn:hover { color: var(--mp-text-primary); background: transparent; } .mp-filter-btn.active { background: var(--mp-accent); color: var(--mp-bg-card); font-weight: 500; } /* Category Dropdown (embedded mode) - matches search input styling */ .mp-filter-select { padding: 8px 32px 8px 12px; font-size: 0.875rem; font-weight: 400; border: 1px solid var(--mp-border); border-radius: var(--mp-radius-sm); background: var(--mp-bg-card); color: var(--mp-text-primary); cursor: pointer; outline: none; appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 10px center; transition: border-color 0.15s ease, box-shadow 0.15s ease; margin-left: auto; /* Right-justify */ } .mp-filter-select:hover { border-color: var(--mp-text-muted); } .mp-filter-select:focus { border-color: var(--mp-accent); box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05); } .mp-filter-select option { background: var(--mp-bg-card); color: var(--mp-text-primary); padding: 8px; } [data-theme="dark"] .mp-filter-select { background-color: #1a1a1a; border-color: #333333; color: #ffffff; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); } [data-theme="dark"] .mp-filter-select:hover { border-color: #555555; } [data-theme="dark"] .mp-filter-select:focus { border-color: #555555; box-shadow: none; } [data-theme="dark"] .mp-filter-select option { background: #1a1a1a; color: #ffffff; } /* Embedded mode: match model manager styling exactly */ body.embedded .mp-filter-select { padding: 8px 28px 8px 12px; background-color: #0a0a0a; border: 1px solid #1a1a1a; border-radius: 6px; color: #666666; font-size: 0.75rem; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E"); } body.embedded .mp-filter-select:hover { border-color: #2a2a2a; } body.embedded .mp-filter-select:focus { border-color: #333333; background-color: #0f0f0f; } body.embedded .mp-filter-select option { background: #0a0a0a; color: #ffffff; } /* === Apps Grid === */ .apps-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(var(--mp-card-min-width), 1fr)); gap: var(--mp-grid-gap); /* Cap at max columns by limiting grid width */ max-width: var(--mp-grid-max-width); } /* === App Card === */ .app-card { background: var(--mp-bg-card); border: 1px solid var(--mp-border); border-radius: var(--mp-radius-md); padding: 16px; transition: box-shadow 0.2s ease, border-color 0.2s ease; cursor: pointer; text-decoration: none; color: inherit; display: flex; flex-direction: column; gap: 12px; } .app-card:hover { border-color: rgba(0, 0, 0, 0.12); box-shadow: var(--mp-shadow-md); } [data-theme="dark"] .app-card { border-color: #333333; } [data-theme="dark"] .app-card:hover { border-color: #444444; } .app-card-header { display: flex; align-items: center; gap: 12px; } .app-logo { width: 44px; height: 44px; border-radius: var(--mp-radius-sm); object-fit: cover; background: var(--mp-bg-page); flex-shrink: 0; } .app-info { flex: 1; min-width: 0; } .app-name { font-size: 0.9375rem; font-weight: 550; color: var(--mp-text-primary); margin: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.3; } .app-categories { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 2px; } .category-tag { font-size: 0.6875rem; color: var(--mp-text-muted); text-transform: capitalize; } .category-tag::after { content: " · "; } .category-tag:last-child::after { content: ""; } .app-description { font-size: 0.8125rem; color: var(--mp-text-secondary); line-height: 1.5; margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .app-links { display: flex; gap: 6px; margin-top: auto; padding-top: 4px; } .app-link { font-size: 0.75rem; padding: 6px 12px; border-radius: 6px; text-decoration: none; transition: background 0.15s ease, border-color 0.15s ease; display: inline-flex; align-items: center; font-weight: 500; border: 1px solid transparent; } .app-link.primary { background: var(--mp-accent); color: var(--mp-bg-card); } .app-link.primary:hover { background: var(--mp-accent-hover); } .app-link.secondary { background: transparent; color: var(--mp-text-secondary); border-color: var(--mp-border); } .app-link.secondary:hover { color: var(--mp-text-primary); border-color: var(--mp-text-muted); } [data-theme="dark"] .app-link.secondary { border-color: #444444; color: #aaaaaa; } [data-theme="dark"] .app-link.secondary:hover { border-color: #666666; color: #ffffff; } /* === States === */ .loading-state, .error-state, .empty-state { display: none; flex-direction: column; align-items: center; justify-content: center; padding: 80px 20px; text-align: center; color: var(--mp-text-secondary); } .loading-state.show, .error-state.show, .empty-state.show { display: flex; } .loading-spinner { width: 24px; height: 24px; border: 2px solid var(--mp-border); border-top-color: var(--mp-accent); border-radius: 50%; animation: spin 0.8s linear infinite; margin-bottom: 12px; } @keyframes spin { to { transform: rotate(360deg); } } .error-icon, .empty-icon { font-size: 2rem; margin-bottom: 12px; opacity: 0.5; } .error-state h2 { font-size: 1rem; font-weight: 500; color: var(--mp-text-primary); margin: 0 0 4px 0; } .error-state p, .empty-state p { font-size: 0.875rem; margin: 0; } .retry-btn { margin-top: 16px; padding: 8px 16px; font-size: 0.8125rem; font-weight: 500; background: var(--mp-accent); color: var(--mp-bg-card); border: none; border-radius: var(--mp-radius-sm); cursor: pointer; transition: background 0.15s ease; } .retry-btn:hover { background: var(--mp-accent-hover); } /* === Responsive === */ /* Embedded mode: use same auto-fit grid (inherits from base) */ /* Phone/narrow screen styles */ @media (max-width: 640px) { .marketplace-main { padding: 0 16px 32px; } .marketplace-hero { padding: 24px 0 16px; } .marketplace-title { font-size: clamp(1.95rem, 8.2vw, 2.4rem); } .search-container { width: 100%; max-width: none; } /* In embedded mode, keep search bar at fixed width */ body.embedded .search-container { width: 240px; max-width: 100%; } .apps-grid { grid-template-columns: 1fr; gap: 10px; } .app-card { padding: 14px; } .app-logo { width: 40px; height: 40px; } } /* Very narrow screens / small embedded panels */ @media (max-width: 400px) { .marketplace-main { padding: 0 10px 32px; } body.embedded .marketplace-controls { padding: 0; } body.embedded .apps-grid { padding: 0; grid-template-columns: 1fr; /* Force single column on very narrow */ } .app-card { padding: 12px; } .app-name { font-size: 0.875rem; } .app-description { font-size: 0.75rem; } .app-link { padding: 5px 10px; font-size: 0.7rem; } } /* === Hide grid when showing states === */ .apps-grid.hidden { display: none; } lemonade-sdk-lemonade-d88f5d9/docs/assets/marketplace.js000066400000000000000000000172241515430344400233650ustar00rootroot00000000000000/** * Marketplace JavaScript * Handles loading, filtering, and displaying apps */ // Configuration const APPS_JSON_URL = 'https://raw.githubusercontent.com/lemonade-sdk/marketplace/main/apps.json'; // State let allApps = []; let categories = []; let selectedCategory = 'all'; let searchQuery = ''; /** * Check if running in embedded mode (inside Electron app) */ function isEmbedded() { const params = new URLSearchParams(window.location.search); return params.get('embedded') === 'true'; } /** * Check if dark theme is requested */ function isDarkTheme() { const params = new URLSearchParams(window.location.search); return params.get('theme') === 'dark'; } /** * Initialize the page */ function initMarketplace() { // Apply embedded mode if (isEmbedded()) { document.documentElement.classList.add('embedded'); document.body.classList.add('embedded'); } // Apply dark theme if (isDarkTheme()) { document.documentElement.setAttribute('data-theme', 'dark'); } // Setup search const searchInput = document.getElementById('search-input'); if (searchInput) { searchInput.addEventListener('input', debounce(handleSearch, 200)); } } /** * Load apps from the JSON file */ async function loadApps() { const grid = document.getElementById('apps-grid'); const loading = document.getElementById('loading-state'); const error = document.getElementById('error-state'); const empty = document.getElementById('empty-state'); // Show loading grid.classList.add('hidden'); loading.classList.add('show'); error.classList.remove('show'); empty.classList.remove('show'); try { const response = await fetch(APPS_JSON_URL); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); allApps = data.apps || []; categories = data.categories || []; // Render categories renderCategories(); // Render apps renderApps(); // Hide loading loading.classList.remove('show'); grid.classList.remove('hidden'); } catch (err) { console.error('Failed to load apps:', err); loading.classList.remove('show'); error.classList.add('show'); } } /** * Render category filter buttons or dropdown (for embedded mode) */ function renderCategories() { const container = document.getElementById('category-filters'); if (!container) return; // In embedded mode, use a dropdown for space efficiency if (isEmbedded()) { let options = ``; for (const cat of categories) { options += ``; } container.innerHTML = ` `; const select = document.getElementById('category-select'); if (select) { select.addEventListener('change', (e) => { selectedCategory = e.target.value; renderApps(); }); } return; } // Non-embedded: use buttons let html = ``; // Create category buttons for (const cat of categories) { html += ``; } container.innerHTML = html; // Add click handlers container.querySelectorAll('.mp-filter-btn').forEach(btn => { btn.addEventListener('click', () => handleCategoryClick(btn)); }); } /** * Handle category button click */ function handleCategoryClick(btn) { // Update active state document.querySelectorAll('.mp-filter-btn').forEach(b => b.classList.remove('active')); btn.classList.add('active'); // Update selected category selectedCategory = btn.dataset.category; // Re-render apps renderApps(); } /** * Handle search input */ function handleSearch(e) { searchQuery = e.target.value.toLowerCase().trim(); renderApps(); } /** * Filter apps based on current state */ function filterApps() { return allApps.filter(app => { // Category filter if (selectedCategory !== 'all') { if (!app.category || !app.category.includes(selectedCategory)) { return false; } } // Search filter if (searchQuery) { const nameMatch = app.name.toLowerCase().includes(searchQuery); const descMatch = app.description.toLowerCase().includes(searchQuery); if (!nameMatch && !descMatch) { return false; } } return true; }); } /** * Render app cards */ function renderApps() { const grid = document.getElementById('apps-grid'); const empty = document.getElementById('empty-state'); const filteredApps = filterApps(); if (filteredApps.length === 0) { grid.classList.add('hidden'); empty.classList.add('show'); return; } grid.classList.remove('hidden'); empty.classList.remove('show'); let html = ''; for (const app of filteredApps) { html += renderAppCard(app); } grid.innerHTML = html; // Prevent card click when clicking links grid.querySelectorAll('.app-link').forEach(link => { link.addEventListener('click', e => e.stopPropagation()); }); // Make cards clickable grid.querySelectorAll('.app-card').forEach(card => { card.addEventListener('click', () => { const url = card.dataset.url; if (url) { window.open(url, '_blank', 'noopener,noreferrer'); } }); }); } /** * Render a single app card */ function renderAppCard(app) { const categoryTags = (app.category || []) .slice(0, 2) .map(cat => `${cat}`) .join(''); // No featured badge - keeping it clean // Build links let linksHtml = ''; if (app.links) { if (app.links.app) { linksHtml += `Visit`; } if (app.links.guide) { linksHtml += `Guide`; } if (app.links.video) { linksHtml += `Video`; } } const logoUrl = app.logo || 'data:image/svg+xml,?'; return `

${escapeHtml(app.name)}

${categoryTags}

${escapeHtml(app.description)}

`; } /** * Escape HTML to prevent XSS */ function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } /** * Debounce function for search */ function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Initialize when DOM is ready document.addEventListener('DOMContentLoaded', initMarketplace); lemonade-sdk-lemonade-d88f5d9/docs/assets/mkdocs_requirements.txt000066400000000000000000000001011515430344400253450ustar00rootroot00000000000000mkdocs mkdocs-material mkdocs-monorepo-plugin pymdown-extensions lemonade-sdk-lemonade-d88f5d9/docs/assets/models.css000066400000000000000000000241061515430344400225310ustar00rootroot00000000000000:root { --models-bg: #fffbe6; --models-surface: #ffffff; --models-surface-soft: #fffcf0; --models-text: #1c1c1c; --models-muted: #6f6a5b; --models-border: rgba(43, 35, 19, 0.11); --models-shadow: 0 10px 24px rgba(27, 22, 12, 0.06); --models-sidebar-bg: #f8f3e3; --models-sidebar-border: rgba(84, 72, 32, 0.16); --models-sidebar-width: 292px; --models-layout-gap: 28px; } #models-page-body { color: var(--models-text); } .models-main { width: min(1480px, calc(100vw - 56px)); margin: 0 auto; padding: 0 0 60px; } .models-hero { padding: 50px 0 38px; } .models-layout { display: grid; grid-template-columns: var(--models-sidebar-width) minmax(0, 1fr); gap: var(--models-layout-gap); align-items: start; } .models-sidebar-toggle { display: none; } .models-sidebar { position: sticky; top: 78px; height: fit-content; display: flex; flex-direction: column; gap: 14px; } .models-sidebar-backdrop { display: none; } .models-content, .models-browser { width: 100%; max-width: none; } .models-search-wrap { position: relative; } .models-search-input { width: 100%; padding: 10px 11px 10px 35px; font-size: 0.9rem; transition: border-color 0.2s ease, box-shadow 0.2s ease; } .models-search-input:focus { outline: none; } .models-search-icon { position: absolute; left: 11px; top: 50%; transform: translateY(-50%); color: #9d9d9d; pointer-events: none; } .backend-filter-buttons { display: flex; gap: 5px; flex-direction: column; } .label-filter-buttons { display: flex; flex-wrap: wrap; gap: 7px; } .state-box { border: 1px dashed var(--models-border); border-radius: 11px; padding: 16px; color: var(--models-muted); background: rgba(255, 255, 255, 0.85); margin: 10px 0 12px; } .retry-btn { margin-top: 8px; border: 1px solid var(--models-border); background: #fff; border-radius: 8px; padding: 7px 11px; cursor: pointer; } .models-context { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 8px 0 14px; color: #6e6654; font-size: 0.82rem; } .models-context-prefix { font-size: 0.74rem; letter-spacing: 0.05em; text-transform: uppercase; color: #84785e; } .models-context-pill { border: 1px solid rgba(79, 66, 30, 0.14); background: rgba(255, 255, 255, 0.52); border-radius: 999px; padding: 3px 8px; color: #5f553d; } .models-context-count { margin-left: auto; font-weight: 600; color: #4a402b; } .models-clear-filters-btn { border: none; background: transparent; color: #6d5d38; font-size: 0.8rem; text-decoration: underline; text-underline-offset: 2px; cursor: pointer; padding: 0; } .models-clear-filters-btn:hover { color: #3f3316; } .hot-picks { margin: 8px 0 18px; padding: 12px 14px 14px; border: 1px solid rgba(79, 66, 30, 0.12); border-radius: 12px; background: rgba(255, 255, 255, 0.45); } .hot-picks-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; } .hot-picks-title { margin: 0; font-size: 1.04rem; font-weight: 640; letter-spacing: -0.01em; color: #2f2818; } .hot-picks-link { border: none; background: transparent; color: #6e5e37; font-size: 0.8rem; text-decoration: underline; text-underline-offset: 2px; cursor: pointer; padding: 0; } .hot-picks-link:hover { color: #3e3217; } .hot-picks-subtitle { margin: 6px 0 0; font-size: 0.83rem; color: #736955; } .hot-picks .model-grid { margin-top: 12px; } .model-sections { display: flex; flex-direction: column; gap: 28px; } .model-section { width: 100%; } .model-section-toggle { position: relative; width: 100%; border: none; border-radius: 0; background: transparent; text-align: left; padding: 0; cursor: pointer; transition: opacity 0.2s ease; display: flex; justify-content: space-between; align-items: baseline; gap: 10px; } .model-section-toggle:hover { opacity: 0.88; } .model-section-title-wrap { display: inline-flex; align-items: center; gap: 0; min-width: 0; } .model-section-title { font-size: clamp(1.2rem, 1.5vw, 1.5rem); font-weight: 640; letter-spacing: -0.014em; line-height: 1.1; color: #282115; } .model-section-chevron { display: none; } .model-section-count { font-size: 0.82rem; font-weight: 500; letter-spacing: 0; text-transform: none; color: #7b725e; border: none; border-radius: 0; padding: 0; background: transparent; white-space: nowrap; } .model-section.is-collapsed .model-section-toggle { opacity: 0.86; } .model-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; margin-top: 12px; width: 100%; } .model-section.is-collapsed .model-grid { display: none; } .model-card { background: var(--models-surface); border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 18px; padding: 20px; transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease; width: 100%; box-sizing: border-box; box-shadow: 0 8px 20px rgba(32, 25, 8, 0.05); } .model-card:hover { border-color: rgba(82, 68, 29, 0.18); box-shadow: 0 12px 26px rgba(32, 25, 8, 0.09); transform: translateY(-1px); } .model-card-head { display: flex; align-items: start; gap: 8px; margin-bottom: 2px; } .model-name { margin: 0 0 10px; font-size: 1.06rem; line-height: 1.35; letter-spacing: -0.01em; color: #201c14; } .badge-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; } .badge { font-size: 0.72rem; border: 1px solid rgba(101, 87, 47, 0.14); border-radius: 999px; padding: 3px 7px; background: #fdf7e6; color: #6a5f46; } .model-pull-wrap { display: flex; align-items: center; gap: 6px; background: #1a1b1f; color: #f5f5f5; border-radius: 9px; border: 1px solid #2c2d33; padding: 7px 8px 7px 11px; box-shadow: none; } .model-pull { margin: 0; flex: 1 1 auto; min-width: 0; background: transparent; border: none; padding: 3px 0; font-size: 0.76rem; color: inherit; overflow-x: auto; } .model-pull-copy-btn { position: relative; flex: 0 0 auto; border: 1px solid rgba(255, 255, 255, 0.18); background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.9); border-radius: 9px; line-height: 0; padding: 0.42rem; cursor: pointer; transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease; } .model-pull-copy-btn svg { width: 0.95rem; height: 0.95rem; display: block; fill: currentColor; } .model-pull-copy-btn:hover, .model-pull-copy-btn:focus { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 224, 102, 0.42); transform: translateY(-1px); } .model-pull-copy-btn.is-copied { color: #ffe680; border-color: rgba(255, 224, 102, 0.55); background: rgba(255, 224, 102, 0.16); } .model-details { margin-top: 14px; border-top: 1px solid rgba(83, 69, 36, 0.1); padding-top: 9px; } .kv-table { width: 100%; border-collapse: collapse; font-size: 0.82rem; } .kv-table td { border-top: 1px solid rgba(83, 69, 36, 0.08); padding: 9px 2px; vertical-align: top; } .kv-table td:first-child { width: 39%; color: #7f7660; } .release-card { display: block; border-radius: 8px; text-decoration: none; color: #2a261a; background: transparent; border: none; padding: 0 0 14px; transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; } .release-card:hover { background: transparent; box-shadow: none; } .release-label { margin-bottom: 8px; } #release-tag-text { display: block; } .release-tag-value { width: 100%; box-sizing: border-box; text-decoration: none; cursor: inherit; } .hidden { display: none !important; } @media (max-width: 1280px) { :root { --models-sidebar-width: 250px; --models-layout-gap: 24px; } .models-layout { grid-template-columns: var(--models-sidebar-width) minmax(0, 1fr); gap: var(--models-layout-gap); } } @media (min-width: 1021px) { .models-hero { width: calc(100% - (var(--models-sidebar-width) + var(--models-layout-gap))); max-width: 1140px; margin-left: calc(var(--models-sidebar-width) + var(--models-layout-gap)); } .models-sidebar { justify-self: start; } .models-content { max-width: 1140px; margin: 0 auto; } } @media (max-width: 1020px) { .models-main { width: min(1200px, calc(100vw - 34px)); } .models-hero { padding: 36px 0 28px; } .models-layout { grid-template-columns: 1fr; gap: 18px; } .models-sidebar-toggle { display: inline-flex; position: sticky; top: 10px; z-index: 122; } .models-sidebar { position: fixed; left: 0; top: 0; width: min(360px, 86vw); max-width: 100%; height: 100dvh; overflow-y: auto; padding: 14px; gap: 10px; background: var(--models-sidebar-bg); border-right: 1px solid rgba(84, 72, 32, 0.26); box-shadow: 0 24px 40px rgba(0, 0, 0, 0.2); z-index: 130; transform: translateX(calc(-100% - 24px)); transition: transform 0.25s ease; } #models-page-body.models-sidebar-open .models-sidebar { transform: translateX(0); } .models-sidebar-backdrop { display: block; position: fixed; inset: 0; background: rgba(0, 0, 0, 0.36); opacity: 0; pointer-events: none; transition: opacity 0.2s ease; z-index: 125; } #models-page-body.models-sidebar-open .models-sidebar-backdrop { opacity: 1; pointer-events: auto; } .model-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 720px) { .models-main { width: calc(100vw - 24px); } .models-sidebar-toggle { top: 8px; } .models-hero { padding: 30px 0 22px; text-align: center; } .models-subtitle { margin: 0 auto; font-size: 1rem; } .models-context { margin: 6px 0 12px; } .models-context-count { margin-left: 0; } .hot-picks { padding: 10px 10px 12px; } .model-section-toggle { padding: 0; } .model-section-title { font-size: 1.1rem; } .model-section-count { font-size: 0.7rem; padding: 3px 7px; } .model-grid { grid-template-columns: 1fr; } } lemonade-sdk-lemonade-d88f5d9/docs/assets/models.js000066400000000000000000000454631515430344400223660ustar00rootroot00000000000000const GITHUB_REPO = 'lemonade-sdk/lemonade'; const TAGS_URL = `https://api.github.com/repos/${GITHUB_REPO}/tags?per_page=100`; const RAW_BASE = 'https://raw.githubusercontent.com/lemonade-sdk/lemonade'; const RECIPE_PRIORITY = [ 'llamacpp', 'ryzenai-llm', 'flm', 'whispercpp', 'sd-cpp', 'oga-hybrid', 'oga-npu', 'oga-cpu', 'kokoro' ]; const RECIPE_DISPLAY_NAMES = { llamacpp: 'llama.cpp GPU', 'ryzenai-llm': 'Ryzen AI SW NPU', flm: 'FastFlowLM NPU', whispercpp: 'whisper.cpp', 'sd-cpp': 'stable-diffusion.cpp' }; const state = { tag: null, sourceUrl: null, models: [], search: '', recipe: 'all', label: 'all' }; function hasLabel(details, label) { const labels = Array.isArray(details.labels) ? details.labels : []; return labels.includes(label); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = String(text); return div.innerHTML; } function toTitle(text) { return String(text) .replace(/_/g, ' ') .replace(/-/g, ' ') .replace(/\b\w/g, (ch) => ch.toUpperCase()); } function slugify(text) { return String(text).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); } function getRecipeDisplayName(recipe) { if (!recipe || recipe === 'unknown') return 'Unknown Recipe'; return RECIPE_DISPLAY_NAMES[recipe] || toTitle(recipe); } function parseCheckpoint(name, details) { const raw = details.checkpoint; if (typeof raw !== 'string') { return { repo: null, variant: null, display: '' }; } if (name.endsWith('-FLM')) { return { repo: raw, variant: null, display: raw }; } const split = raw.split(':'); if (split.length > 1 && split[0].includes('/')) { return { repo: split[0], variant: split.slice(1).join(':'), display: raw }; } if (raw.includes('/')) { return { repo: raw, variant: null, display: raw }; } return { repo: null, variant: null, display: raw }; } function modelMatchesCatalogFilters(model) { const { name, details } = model; if (!details.suggested) { return false; } const recipe = details.recipe || 'unknown'; if (state.recipe !== 'all' && recipe !== state.recipe) { return false; } if (!state.search) { return true; } const labels = Array.isArray(details.labels) ? details.labels.join(' ') : ''; const blob = `${name} ${details.checkpoint || ''} ${details.recipe || ''} ${labels}`.toLowerCase(); return blob.includes(state.search); } function modelMatchesFilters(model) { if (!modelMatchesCatalogFilters(model)) { return false; } if (state.label !== 'all' && !hasLabel(model.details, state.label)) { return false; } return true; } function getRecipeValues() { const values = new Set(); state.models.forEach((model) => { if (model.details && model.details.suggested) { values.add(model.details.recipe || 'unknown'); } }); return [...values].sort((a, b) => { const aIndex = RECIPE_PRIORITY.indexOf(a); const bIndex = RECIPE_PRIORITY.indexOf(b); if (aIndex >= 0 && bIndex >= 0) return aIndex - bIndex; if (aIndex >= 0) return -1; if (bIndex >= 0) return 1; return a.localeCompare(b); }); } function getLabelValues() { const values = new Set(['hot']); state.models.forEach((model) => { if (model.details && model.details.suggested && Array.isArray(model.details.labels)) { model.details.labels.forEach((label) => values.add(label)); } }); return [...values].sort((a, b) => a.localeCompare(b)); } function renderRecipeButtons() { const container = document.getElementById('recipe-filter-buttons'); const recipes = getRecipeValues(); const buttons = [{ key: 'all', title: 'All recipes' }, ...recipes.map((recipe) => ({ key: recipe, title: getRecipeDisplayName(recipe) }))]; container.innerHTML = buttons .map((item) => ` `) .join(''); container.querySelectorAll('.backend-filter-btn').forEach((button) => { button.addEventListener('click', () => { state.recipe = button.dataset.recipe; renderRecipeButtons(); renderModels(); }); }); } function renderLabelButtons() { const container = document.getElementById('label-filter-buttons'); const labels = getLabelValues().filter((label) => label !== 'hot' && label !== 'all'); const buttons = [ { key: 'hot', title: '🔥 hot' }, { key: 'all', title: 'All labels' }, ...labels.map((label) => ({ key: label, title: label })) ]; container.innerHTML = buttons .map((item) => ` `) .join(''); container.querySelectorAll('.label-filter-btn').forEach((button) => { button.addEventListener('click', () => { state.label = button.dataset.label; renderLabelButtons(); renderModels(); }); }); } function buildModelTableRows(name, details) { const rows = []; const checkpoint = parseCheckpoint(name, details); if (checkpoint.repo) { rows.push({ key: 'Checkpoint', value: `${escapeHtml(checkpoint.repo)}` }); if (checkpoint.variant) { rows.push({ key: 'GGUF Variant', value: escapeHtml(checkpoint.variant) }); } } else if (details.checkpoint) { rows.push({ key: 'Checkpoint', value: escapeHtml(details.checkpoint) }); } for (const [key, value] of Object.entries(details)) { if (['checkpoint', 'max_prompt_length', 'suggested', 'labels'].includes(key)) { continue; } if (key === 'image_defaults' && value && typeof value === 'object') { if (value.steps !== undefined) rows.push({ key: 'Default Steps', value: escapeHtml(value.steps) }); if (value.cfg_scale !== undefined) rows.push({ key: 'Default CFG Scale', value: escapeHtml(value.cfg_scale) }); if (value.width !== undefined && value.height !== undefined) { rows.push({ key: 'Default Size', value: `${escapeHtml(value.width)}x${escapeHtml(value.height)}` }); } continue; } if (key === 'size') { rows.push({ key: 'Size (GB)', value: escapeHtml(value) }); continue; } rows.push({ key: toTitle(key), value: escapeHtml(value) }); } return rows; } function renderModelCard(model, options = {}) { const { compact = false } = options; const { name, details } = model; const labels = Array.isArray(details.labels) ? details.labels : []; const tableRows = buildModelTableRows(name, details); const pullCommand = `lemonade-server pull ${name}`; const badgeMarkup = [ ...labels.map((label) => `${escapeHtml(label)}`) ].join(''); const rowMarkup = tableRows .map((row) => `${escapeHtml(row.key)}${row.value}`) .join(''); return `

${escapeHtml(name)}

${badgeMarkup}
${escapeHtml(pullCommand)}
${compact ? '' : `
${rowMarkup}
`}
`; } function wireSectionToggles() { document.querySelectorAll('.model-section-toggle').forEach((button) => { button.addEventListener('click', () => { const sectionId = button.getAttribute('data-section-id'); const sectionEl = document.getElementById(sectionId); if (!sectionEl) return; const willCollapse = !sectionEl.classList.contains('is-collapsed'); sectionEl.classList.toggle('is-collapsed', willCollapse); const chevron = button.querySelector('.model-section-chevron'); if (chevron) { chevron.textContent = willCollapse ? '▸' : '▾'; } }); }); } function recipeDisplayName(recipe) { return getRecipeDisplayName(recipe); } function labelDisplayName(label) { if (label === 'all') return 'All labels'; if (label === 'hot') return 'Hot'; return toTitle(label); } function wireModelCardCopyButtons() { document.querySelectorAll('.model-pull-copy-btn').forEach((button) => { button.addEventListener('click', async () => { const text = button.dataset.copyText || ''; if (!text) return; try { await navigator.clipboard.writeText(text); } catch (error) { const helper = document.createElement('textarea'); helper.value = text; document.body.appendChild(helper); helper.select(); document.execCommand('copy'); document.body.removeChild(helper); } button.classList.add('is-copied'); button.setAttribute('aria-label', 'Pull command copied'); button.setAttribute('title', 'Pull command copied'); setTimeout(() => { button.classList.remove('is-copied'); button.setAttribute('aria-label', 'Copy pull command'); button.setAttribute('title', 'Copy pull command'); }, 1200); }); }); } function renderModels() { const sectionsHost = document.getElementById('supported-model-sections'); const loading = document.getElementById('models-loading'); const error = document.getElementById('models-error'); const empty = document.getElementById('models-empty'); const context = document.getElementById('models-context'); const hotPicksSection = document.getElementById('hot-picks-section'); const hotPicksGrid = document.getElementById('hot-picks-grid'); const hotPicksTitle = document.querySelector('.hot-picks-title'); loading.classList.add('hidden'); error.classList.add('hidden'); const baseFiltered = state.models.filter(modelMatchesCatalogFilters); const filtered = state.label === 'all' ? baseFiltered : baseFiltered.filter((model) => hasLabel(model.details, state.label)); const hotCandidates = baseFiltered.filter((model) => hasLabel(model.details, 'hot')); const countText = `${filtered.length} model${filtered.length === 1 ? '' : 's'}`; const showClear = state.recipe !== 'all' || state.label !== 'all' || !!state.search; const searchText = state.search ? `Search: "${state.search}"` : ''; context.innerHTML = ` Showing ${escapeHtml(labelDisplayName(state.label))} ${escapeHtml(state.recipe === 'all' ? 'All recipes' : getRecipeDisplayName(state.recipe))} ${searchText ? `${escapeHtml(searchText)}` : ''} ${escapeHtml(countText)} ${showClear ? '' : ''} `; context.classList.remove('hidden'); if (state.label !== 'hot' && hotCandidates.length > 0) { const hotPicks = hotCandidates.slice(0, 6); hotPicksTitle.textContent = 'Hot models'; hotPicksGrid.innerHTML = hotPicks.map((model) => renderModelCard(model, { compact: true })).join(''); hotPicksSection.classList.remove('hidden'); } else { hotPicksGrid.innerHTML = ''; hotPicksSection.classList.add('hidden'); } if (filtered.length === 0) { sectionsHost.classList.add('hidden'); sectionsHost.innerHTML = ''; empty.classList.remove('hidden'); wireModelCardCopyButtons(); return; } empty.classList.add('hidden'); sectionsHost.classList.remove('hidden'); const groupedByRecipe = new Map(); filtered.forEach((model) => { const key = model.details.recipe || 'unknown'; if (!groupedByRecipe.has(key)) groupedByRecipe.set(key, []); groupedByRecipe.get(key).push(model); }); const orderedRecipes = getRecipeValues().filter((recipe) => groupedByRecipe.has(recipe)); if (groupedByRecipe.has('unknown') && !orderedRecipes.includes('unknown')) { orderedRecipes.push('unknown'); } const sectionHtml = []; orderedRecipes.forEach((recipe) => { const models = groupedByRecipe.get(recipe); const cards = models.map(renderModelCard).join(''); const sectionId = `section-recipe-${slugify(recipe)}`; const isCollapsedByDefault = false; const modelCount = `${models.length} model${models.length === 1 ? '' : 's'}`; sectionHtml.push(`
${cards}
`); }); sectionsHost.innerHTML = sectionHtml.join(''); wireSectionToggles(); wireModelCardCopyButtons(); } async function fetchLatestVTag() { const response = await fetch(TAGS_URL); if (!response.ok) { throw new Error(`Tag lookup failed: HTTP ${response.status}`); } const tags = await response.json(); if (!Array.isArray(tags)) { throw new Error('Tag response is invalid'); } const match = tags.find((tag) => typeof tag.name === 'string' && /^v/.test(tag.name)); if (!match) { throw new Error('No v* tag found'); } return match.name; } async function loadModelsData() { const loading = document.getElementById('models-loading'); const error = document.getElementById('models-error'); const sectionsHost = document.getElementById('supported-model-sections'); const empty = document.getElementById('models-empty'); const context = document.getElementById('models-context'); const hotPicksSection = document.getElementById('hot-picks-section'); const hotPicksGrid = document.getElementById('hot-picks-grid'); loading.classList.remove('hidden'); error.classList.add('hidden'); sectionsHost.classList.add('hidden'); empty.classList.add('hidden'); context.classList.add('hidden'); hotPicksSection.classList.add('hidden'); hotPicksGrid.innerHTML = ''; const tag = await fetchLatestVTag(); const sourceUrl = `${RAW_BASE}/${tag}/src/cpp/resources/server_models.json`; const response = await fetch(sourceUrl); if (!response.ok) { throw new Error(`Model JSON fetch failed: HTTP ${response.status}`); } const data = await response.json(); if (!data || typeof data !== 'object' || Array.isArray(data)) { throw new Error('Model JSON payload is invalid'); } state.tag = tag; state.sourceUrl = sourceUrl; state.models = Object.entries(data).map(([name, details]) => ({ name, details })); const releaseTagLink = document.getElementById('release-tag-link'); const releaseTagText = document.getElementById('release-tag-text'); releaseTagText.textContent = `Lemonade ${tag}`; releaseTagLink.href = sourceUrl; releaseTagLink.classList.remove('hidden'); renderRecipeButtons(); renderLabelButtons(); renderModels(); } function wireControls() { const searchInput = document.getElementById('model-search-input'); const retryButton = document.getElementById('retry-load-btn'); const modelsContext = document.getElementById('models-context'); const hotPicksViewAllButton = document.getElementById('hot-picks-view-all-btn'); const sidebarToggle = document.getElementById('models-sidebar-toggle'); const sidebar = document.getElementById('models-sidebar'); const sidebarBackdrop = document.getElementById('models-sidebar-backdrop'); const pageBody = document.getElementById('models-page-body'); function setSidebarOpen(isOpen) { if (!sidebarToggle || !sidebar || !sidebarBackdrop || !pageBody) return; pageBody.classList.toggle('models-sidebar-open', isOpen); sidebarToggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false'); sidebarBackdrop.setAttribute('aria-hidden', isOpen ? 'false' : 'true'); } searchInput.addEventListener('input', () => { state.search = searchInput.value.trim().toLowerCase(); if (state.label === 'hot') { state.label = 'all'; renderLabelButtons(); } renderModels(); }); retryButton.addEventListener('click', () => { initializeModelsPage(); }); if (hotPicksViewAllButton) { hotPicksViewAllButton.addEventListener('click', () => { state.label = 'hot'; renderLabelButtons(); renderModels(); const sections = document.getElementById('supported-model-sections'); if (sections) { sections.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); } if (modelsContext) { modelsContext.addEventListener('click', (event) => { const clearButton = event.target.closest('#models-clear-filters-btn'); if (!clearButton) return; state.recipe = 'all'; state.label = 'all'; state.search = ''; searchInput.value = ''; renderRecipeButtons(); renderLabelButtons(); renderModels(); }); } if (sidebarToggle && sidebar && sidebarBackdrop && pageBody) { sidebarToggle.addEventListener('click', () => { const willOpen = !pageBody.classList.contains('models-sidebar-open'); setSidebarOpen(willOpen); }); sidebarBackdrop.addEventListener('click', () => { setSidebarOpen(false); }); document.addEventListener('keydown', (event) => { if (event.key === 'Escape') { setSidebarOpen(false); } }); sidebar.addEventListener('click', (event) => { const targetButton = event.target.closest('.backend-filter-btn, .label-filter-btn'); if (targetButton && window.matchMedia('(max-width: 1020px)').matches) { setSidebarOpen(false); } }); window.addEventListener('resize', () => { if (window.innerWidth > 1020) { setSidebarOpen(false); } }); } } async function initializeModelsPage() { const loading = document.getElementById('models-loading'); const error = document.getElementById('models-error'); try { if (!window.__modelsPageControlsBound) { wireControls(); window.__modelsPageControlsBound = true; } await loadModelsData(); } catch (err) { console.error('Failed to initialize models page:', err); loading.classList.add('hidden'); error.classList.remove('hidden'); } } lemonade-sdk-lemonade-d88f5d9/docs/assets/navbar.js000066400000000000000000000017431515430344400223450ustar00rootroot00000000000000// Shared Navbar Component for Lemonade docs pages function createNavbar(basePath = '') { return ` `; } function initializeNavbar(basePath = '') { const navbarContainer = document.querySelector('.navbar-placeholder'); if (navbarContainer) { navbarContainer.innerHTML = createNavbar(basePath); } else { console.warn('Navbar placeholder not found'); } } if (typeof module !== 'undefined' && module.exports) { module.exports = { createNavbar, initializeNavbar }; } lemonade-sdk-lemonade-d88f5d9/docs/assets/news-data.js000066400000000000000000000457701515430344400227670ustar00rootroot00000000000000// News content data - easy to add new entries const newsData = [ { title: "Lemonade by AMD: A Unified API for Local AI Developers", url: "https://www.amd.com/en/developer/resources/technical-articles/2026/lemonade-for-local-ai.html", date: "February 10, 2026", description: "Learn how Lemonade delivers a unified, local-first API for text, image, and speech AI across Ryzen AI, Radeon, and broader AI PC hardware.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/technical-blogs/lemonade-sdk/lemonadeheadergraphicsized.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "AMD puts Strix Halo loaded with 8 AI models in a bun-fight: Is a hot dog a sandwich?", url: "https://videocardz.com/newz/amd-puts-strix-halo-loaded-with-8-ai-models-in-a-bun-fight-is-a-hot-dog-a-sandwich", date: "December 24, 2025", description: "VideoCardz covers AMD Strix Halo running multiple AI models locally and explores performance and positioning for AI PCs.", image: "https://cdn.videocardz.com/1/2025/12/RYZEN-AI-MAX-SANDWICH-HOTDOG-1-1200x624.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Ryzen AI and Radeon are ready to run LLMs Locally", url: "https://www.amd.com/en/developer/resources/technical-articles/2025/ryzen-ai-radeon-llms-with-lemonade.html", date: "November 13, 2025", description: "Learn about LLM use cases that run on a variety of AMD PCs.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/generic-thumbnails/9851-industry-applications-use-cases-02.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Lemonade for GitHub Copilot", url: "https://youtu.be/HUwGxlH3yAg?si=0P-S48_PgrWy9WwI", date: "October 14, 2025", description: "Watch how Lemonade brings local AI coding power to VS Code GitHub Copilot Chat—optimized for AMD Ryzen™ AI PCs.", image: "https://img.youtube.com/vi/HUwGxlH3yAg/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Dify + Lemonade for Agentic Workflows", url: "https://www.amd.com/en/developer/resources/technical-articles/2025/harnessing-dify-and-local-llms-on-ryzen-ai-pcs-for-private-workf.html", date: "October 6, 2025", description: "Lemonade now available with Dify, enabling private AI workflows to run locally. Build powerful LLM + RAG apps without cloud dependencies or ML expertise.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/technical-blogs/harnessing-dify---local-llms-on-amd-ryzen-ai-pcs-for-private-workflows/DifyChatExample.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Introducing Lemonade Arcade", url: "https://lemonade-server.ai/news/lemonade-arcade.html", date: "August 22, 2025", description: "We're excited to announce Lemonade Arcade, a new application that transforms your GPU into a creative engine for generating retro-style games using AI.", image: "https://github.com/lemonade-sdk/lemonade-arcade/blob/main/img/banner.png?raw=true", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Run OpenAI's gpt-oss locally with Lemonade", url: "https://lemonade-server.ai/news/gpt-oss.html", date: "August 12, 2025", description: "Lemonade now supports OpenAI's gpt-oss models, bringing you the power to run these cutting-edge models locally on your own hardware! 🎉", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "llamacpp+ROCm7 beta is now supported on Lemonade", url: "https://www.reddit.com/r/LocalLLaMA/comments/1mjgj2x/llamacpprocm7_beta_is_now_supported_on_lemonade/", date: "August 6, 2025", description: "Unlock the power of ROCm7 on your STX Halo or Radeon GPU with Lemonade 8.1.1.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/rocm_hero.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "AMD, ISV Security Partners Collaborate to Protect the AI PC", url: "https://www.amd.com/en/blogs/2025/isv-security-experiences-with-Ryzen-PRO.html", date: "August 05, 2025", description: "Lemonade Server and Ryzen™ AI NPUs bring real-time, on-device protection against phishing, deepfakes, and prompt attacks. No cloud lag, just smarter security where it counts.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/projects/isv-commercial-security-experiences-blog/security-shield-key-art.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Lemonade Server & Open WebUI", url: "https://www.youtube.com/watch?v=yZs-Yzl736E", date: "July 31, 2025", description: "Easily integrate Lemonade Server with Open WebUI to unlock powerful local LLM capabilities on your PC. This video guides you through the installation and setup process.", image: "https://img.youtube.com/vi/yZs-Yzl736E/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Styrk AI & AMD: Guardrails for Your On-Device AI Revolution", url: "https://styrk.ai/styrk-ai-and-amd-guardrails-for-your-on-device-ai-revolution/", date: "July 14, 2025", description: "AMD and Styrk AI bring real-time, on-device LLM security. Powered by AMD NPUs, GPUs, and Lemonade Server, with built-in guardrails for filtering, adversarial detection, and prompt injection defense.", image: "https://styrk.ai/wp-content/uploads/2025/07/styrk-ai_amd-768x432.webp", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "AMD and XMPro Deliver Autonomous Intelligence at the Edge", url: "https://www.amd.com/en/developer/resources/technical-articles/2025/empowering-local-industrial-compute.html", date: "July 15, 2025", description: "AMD and XMPro have partnered to bring advanced AI capabilities to the industrial edge using AMD hardware and the Lemonade Server for efficient, private, local AI workloads.", image: "https://www.amd.com/en/developer/resources/technical-articles/2025/empowering-local-industrial-compute/_jcr_content/_cq_featuredimage.coreimg.jpeg/1752678519383/3598267-amd-x-xmpro-image-edit-1200x627-no-copy.jpeg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Rethinking Local AI: Lemonade Server’s Python Advantage", url: "https://www.amd.com/en/developer/resources/technical-articles/2025/rethinking-local-ai-lemonade-servers-python-advantage.html", date: "July 21, 2025", description: "Learn about why we chose Python for deploying local LLMs with Lemonade and how integrating with your app is incredibly easy.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/technical-blogs/rethinking-local-ai/figure%203%20our%20approach%20to%20python%20gives%20us%20both%20development%20agility%20and%20the%20expected%20level%20of%20production%20readines.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Minions: On-Device and Cloud Language Model Collaboration on AMD Ryzen™ AI", url: "https://www.amd.com/en/developer/resources/technical-articles/2025/minions--on-device-and-cloud-language-model-collaboration-on-ryz.html", date: "July 08, 2025", description: "Minions, a new framework from Stanford’s Hazy Research Group, lets cloud models collaborate with lighter ones on-device—and now runs on Ryzen AI via AMD’s Lemonade Server.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/technical-blogs/minions-on-device-and-cloud-language-model-collaboration-on-ryzen-ai/3546073_Minions_Blog_Banner_1200x600.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Local Tiny Agents: MCP Agents on Ryzen™ AI with Lemonade Server", url: "https://www.amd.com/en/developer/resources/technical-articles/2025/local-tiny-agents--mcp-agents-on-ryzen-ai-with-lemonade-server.html", date: "June 10, 2025", description: "Model Context Protocol (MCP) is now available on AMD Ryzen™ AI PCs and can be used by installing AMD Lemonade Server and connecting it to projects like Hugging Face's Tiny Agents via streaming tool calls.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/technical-blogs/local-tiny-agents--mcp-agents-on-ryzen-ai-with-lemonade-server/lemonade.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Unlocking a Wave of LLM Apps on Ryzen™ AI through Lemonade Server", url: "https://www.amd.com/en/developer/resources/technical-articles/unlocking-a-wave-of-llm-apps-on-ryzen-ai-through-lemonade-server.html", date: "April 17, 2025", description: "Lemonade Server enables LLM acceleration on Windows and Linux without code changes—using hybrid NPU+iGPU execution on Ryzen™ AI 300-series PCs and GPU acceleration on Linux.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/projects/technical-blogs/3328050-lemonade-server/3328050-lemonade-server-blog.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "LLMs on AMD Ryzen™ AI PCs", url: "https://www.youtube.com/watch/qMdMJF89c8g", date: "March 31, 2025", description: "The video explains how Ryzen™ AI 300-series PCs use NPUs and integrated GPUs to accelerate LLMs through hybrid task partitioning, and introduces the Ryzen AI Software Stack.", image: "https://img.youtube.com/vi/qMdMJF89c8g/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Running LLMs on AMD Ryzen™ AI PCs using Lemonade SDK", url: "https://www.youtube.com/watch/Ys7n5OouwtI", date: "March 31, 2025", description: "Watch how the Lemonade SDK lets you experiment with LLMs on Ryzen™ AI 300-series PCs using high-level APIs, including setup and prompting via its CLI.", image: "https://img.youtube.com/vi/Ys7n5OouwtI/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Using the Lemonade SDK", url: "https://www.youtube.com/watch/1_QU_w1zF7Y", date: "March 31, 2025", description: "Watch to see how the Lemonade SDK can be used to run performance benchmarks and evaluate model accuracy using the MMLU test suite.", image: "https://img.youtube.com/vi/1_QU_w1zF7Y/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Integrating Lemonade into your Python App", url: "https://www.youtube.com/watch/aeHRGzxxYRQ", date: "March 31, 2025", description: "This video shows how to use the Lemonade SDK to integrate LLMs into an app, enhancing a basic search tool and running it locally on Ryzen™ AI 300-series PCs.", image: "https://img.youtube.com/vi/aeHRGzxxYRQ/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Lemonade Server + Open WebUI", url: "https://www.youtube.com/watch/PXNTDZREJ_A", date: "March 31, 2025", description: "Introducing Lemonade Server with Open WebUI integration. See how easy it is to install Lemonade Server, download models and get Open WebUI running LLMs on your local PC.", image: "https://img.youtube.com/vi/PXNTDZREJ_A/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Lemonade Server + Microsoft AI Toolkit", url: "https://www.youtube.com/watch/JecpotOZ6qo", date: "April 24, 2025", description: "Introducing Lemonade Server with Microsoft AI Toolkit integration. See how easy it is to install Lemonade Server, download models and get Microsoft AI Toolkit running LLMs on your local PC.", image: "https://img.youtube.com/vi/PXNTDZREJ_A/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Lemonade Server + Continue AI Coding Assistant", url: "https://www.youtube.com/watch/bP_MZnDpbUc", date: "April 30, 2025", description: "Introducing Lemonade Server with Continue AI Coding Assistant integration. See how easy it is to install Lemonade Server, download models and get Continue AI Coding Assistant running LLMs on your local PC.", image: "https://img.youtube.com/vi/bP_MZnDpbUc/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Lemonade Server + PEEL for Local LLM Support in PowerShell", url: "https://www.youtube.com/watch/A-8QYktB0Io", date: "June 6, 2025", description: "Introducing Lemonade Server with PEEL for Local LLM Support in PowerShell integration. See how easy it is to install Lemonade Server, download models and get PEEL running LLMs on your local PC.", image: "https://img.youtube.com/vi/A-8QYktB0Io/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Introducing Lemonade Server for Local LLM Server with GPU and NPU Acceleration", url: "https://www.youtube.com/watch/mcf7dDybUco", date: "July 14, 2025", description: "See how to install Lemonade Server and run LLMs locally with GPU and NPU acceleration on your PC—no code changes needed.", image: "https://img.youtube.com/vi/mcf7dDybUco/maxresdefault.jpg", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "video" }, { title: "Hugging Face MCP Course: https://huggingface.co/learn/mcp-course/unit2/lemonade-server", url: "https://huggingface.co/learn/mcp-course/unit2/lemonade-server", date: "July 8, 2025", description: "AMD Partnered with Hugging Face to provide a guide on how to accelerate our end-to-end Tiny Agents application using AMD Neural Processing Unit (NPU) and integrated GPU (iGPU).", image: "https://huggingface.co/datasets/mcp-course/images/resolve/main/unit0/1.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "tutorial" }, { title: "GAIA: An Open-Source Project from AMD for Running Local LLMs on Ryzen™ AI", url: "https://www.amd.com/en/developer/resources/technical-articles/gaia-an-open-source-project-from-amd-for-running-local-llms-on-ryzen-ai.html", date: "March 20, 2025", description: "GAIA is an application with multiple agents that runs local LLMs on Ryzen™ AI using Lemonade Server.", image: "https://www.amd.com/content/dam/amd/en/images/blogs/designs/projects/technical-blogs/3328050-lemonade-server/3328050-lemonade-server-blog.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "blog" }, { title: "Lemonade Server v8.1.0 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v8.1.0", date: "July 30, 2025", description: "Support for Ryzen™ AI Software v1.5.0 and NPU-only execution.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, { title: "Lemonade Server v8.0.6 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v8.0.6", date: "July 17, 2025", description: "Overhauled llamacpp support in the Lemonade Developer CLI.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, { title: "Lemonade Server v8.0.5 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v8.0.5", date: "July 14, 2025", description: "Added device enumeration capability on Windows and Linux to `lemonade system-info` command.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, { title: "Lemonade Server v8.0.4 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v8.0.4", date: "July 09, 2025", description: "Added `reranking` and `embeddings` support to Lemonade Server.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, { title: "Lemonade Server v8.0.3 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v8.0.3", date: "June 27, 2025", description: "Support for large (sharded) GGUF models. Added `Llama-4-Scout-17B-16E-Instruct-GGUF` to server models list.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, { title: "Lemonade Server v8.0.0 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v8.0.0", date: "June 19, 2025", description: "Major release with Ubuntu support, model manager, and Windows tray app.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, { title: "Lemonade Server v7.0.2 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v7.0.2", date: "June 02, 2025", description: "lm-evaluation-harness is now fully integrated as an automated Lemonade CLI tool.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, { title: "Lemonade Server v7.0.1 Release", url: "https://github.com/lemonade-sdk/lemonade/releases/tag/v7.0.1", date: "May 30, 2025", description: "Added support for GGUF models and llama.cpp backend to Lemonade Server.", image: "https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/banner.png", imageStyle: "width: 100%; height: 100%; object-position: center top; ", type: "release" }, ]; lemonade-sdk-lemonade-d88f5d9/docs/assets/news.css000066400000000000000000000063171515430344400222260ustar00rootroot00000000000000:root { --news-shell-width: min(1480px, calc(100vw - 56px)); --news-sidebar-width: 292px; --news-gap: 28px; } .news-sidebar { top: 150px; left: max(16px, calc((100vw - var(--news-shell-width)) / 2)); width: var(--news-sidebar-width); height: auto; background: transparent; border-right: none; box-shadow: none; padding: 0; z-index: 110; } .news-sidebar .sidebar-card { border: none; border-bottom: 1px solid rgba(77, 65, 31, 0.16); border-radius: 0; background: transparent; padding: 0 0 14px; margin-bottom: 14px; } .news-sidebar .sidebar-card:last-child { border-bottom: none; margin-bottom: 0; } .sidebar-header { margin-bottom: 0; } .sidebar-header p { margin: 0; font-size: 0.84rem; color: #6f6a5b; } .filter-container { gap: 5px; margin: 0; } .sort-container { margin: 0; } .sort-label { display: none; } .news-view { width: var(--news-shell-width); max-width: none; margin: 0 auto; padding: 42px 0 44px calc(var(--news-sidebar-width) + var(--news-gap)); box-sizing: border-box; } .news-view .hero-section { margin: 0 auto 26px; padding: 0; } .news-view .main-heading, .news-view .subtitle { animation: none !important; } .news-view .subtitle { margin-bottom: 28px; } .news-posts { gap: 16px; } .news-tile { border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 14px; box-shadow: 0 8px 20px rgba(32, 25, 8, 0.05); aspect-ratio: auto; height: auto; } .news-tile:hover { transform: translateY(-2px); box-shadow: 0 12px 26px rgba(32, 25, 8, 0.09); } .news-tile-link { height: auto; } .news-image { height: auto; aspect-ratio: 16 / 9; overflow: hidden; } .news-image img { width: 100%; height: 100%; object-fit: cover; } .news-content h3 { overflow-wrap: anywhere; } .sidebar-toggle { display: none; } @media (max-width: 1020px) { :root { --news-shell-width: min(1200px, calc(100vw - 34px)); } .news-view { padding-left: calc(var(--news-sidebar-width) + 20px); } } @media (max-width: 768px) { :root { --news-shell-width: calc(100vw - 24px); } .news-sidebar { top: 0; left: 0; width: min(360px, 86vw); height: 100dvh; background: #f8f3e3; transform: translateX(calc(-100% - 24px)); transition: transform 0.25s ease; border-right: 1px solid rgba(84, 72, 32, 0.26); box-shadow: 0 24px 40px rgba(0, 0, 0, 0.2); z-index: 130; padding: 14px; } .news-sidebar.mobile-open { transform: translateX(0); } .news-sidebar .sidebar-card { margin-bottom: 10px; padding-bottom: 10px; } .sidebar-overlay { display: block; opacity: 0; pointer-events: none; background: rgba(0, 0, 0, 0.36); transition: opacity 0.2s ease; z-index: 125; } .sidebar-overlay.active { opacity: 1; pointer-events: auto; } .sidebar-toggle { display: inline-flex; position: sticky; top: 10px; z-index: 122; align-self: flex-start; } .news-view { width: var(--news-shell-width); padding: 30px 0 36px; display: flex; flex-direction: column; gap: 18px; } .news-view .hero-section { text-align: center; margin-bottom: 0; } } @media (max-width: 720px) { .sidebar-toggle { top: 8px; } } lemonade-sdk-lemonade-d88f5d9/docs/assets/website-styles.css000066400000000000000000003155661515430344400242460ustar00rootroot00000000000000/* Lemonade Website Styles */ /* === CSS Variables === */ :root { /* Colors */ --primary-yellow: #ffe066; --primary-yellow-dark: #ffd43b; --primary-yellow-darker: #ffcc00; --accent-gold: #e6b800; --accent-gold-dark: #bfa100; --text-primary: #222; --text-secondary: #555; --text-muted: #666; --text-light: #999; --text-success: #4ca64c; --bg-primary: #fffbe9; --bg-secondary: #fff8dd; --bg-tertiary: #fff5d1; --bg-white: #fff; --bg-overlay: rgba(255, 251, 233, 0.95); --border-light: #e6e6e6; --border-medium: #ccc; --border-primary: #e6b800; --shadow-light: 0 2px 8px rgba(230, 184, 0, 0.3); --shadow-medium: 0 4px 12px rgba(230, 184, 0, 0.4); --shadow-heavy: 0 8px 24px rgba(0, 0, 0, 0.15); /* Spacing */ --spacing-xs: 0.25rem; --spacing-sm: 0.5rem; --spacing-md: 1rem; --spacing-lg: 1.5rem; --spacing-xl: 2rem; --spacing-xxl: 3rem; /* Typography */ --font-family: 'Inter', 'Segoe UI', 'Arial', sans-serif; --font-size-xs: 0.75rem; --font-size-sm: 0.85rem; --font-size-base: 1rem; --font-size-lg: 1.2rem; --font-size-xl: 1.5rem; --font-size-xxl: 2rem; --font-size-xxxl: 2.5rem; /* Transitions */ --transition-fast: 0.2s ease; --transition-medium: 0.3s ease; --transition-slow: 0.4s ease; /* Border radius */ --radius-sm: 6px; --radius-md: 8px; --radius-lg: 12px; --radius-xl: 18px; --radius-xxl: 24px; --radius-round: 50%; } /* === Base Styles === */ * { box-sizing: border-box; } html, body { overflow-x: hidden; width: 100%; } body { margin: 0; font-family: var(--font-family); background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 50%, var(--bg-tertiary) 100%); color: var(--text-primary); min-height: 100vh; display: flex; flex-direction: column; scroll-behavior: smooth; } body::before { content: ''; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(circle at 20% 20%, rgba(255, 224, 102, 0.1) 0%, transparent 50%), radial-gradient(circle at 80% 80%, rgba(255, 212, 59, 0.1) 0%, transparent 50%); pointer-events: none; z-index: -1; } body.install-options { align-items: center; justify-content: flex-start; } /* === Navigation === */ .navbar { display: flex; justify-content: space-between; align-items: center; padding: var(--spacing-md) var(--spacing-xxl) var(--spacing-sm) var(--spacing-md); font-size: 1.25rem; font-weight: 500; background: transparent; letter-spacing: 0.02em; position: relative; transition: var(--transition-medium); } .navbar-brand { display: flex; align-items: center; } .brand-title { font-size: var(--font-size-xl); font-weight: 700; color: var(--text-primary); text-decoration: none; letter-spacing: 0.01em; } .navbar-context { position: absolute; left: var(--spacing-xl); font-size: 0.95rem; color: var(--text-muted); display: none; align-items: center; white-space: nowrap; } .navbar-context.show { display: flex; } .navbar-links { display: flex; gap: 2.5rem; align-items: center; font-size: 1.22rem; line-height: 1.1; } .navbar a { color: #444; text-decoration: none; transition: var(--transition-fast); } .navbar-links a, .navbar-brand .brand-title a { display: inline-flex; align-items: center; line-height: 1.15; } .navbar-links a { font-size: 1.22rem; font-weight: 500; } .navbar a:hover { color: var(--accent-gold); } .navbar-back { color: var(--accent-gold); text-decoration: none; font-weight: 500; transition: var(--transition-fast); cursor: pointer; margin-right: var(--spacing-sm); } .navbar-back:hover { color: var(--accent-gold-dark); } /* Breadcrumb */ .breadcrumb { position: fixed; top: 0; left: 0; width: 100%; background: var(--bg-overlay); backdrop-filter: blur(10px); border-bottom: 1px solid rgba(230, 184, 0, 0.2); padding: 0.8rem var(--spacing-xl); z-index: 200; display: flex; align-items: center; font-size: 0.95rem; color: var(--text-muted); min-height: 60px; } .breadcrumb-back { color: var(--accent-gold); text-decoration: none; font-weight: 500; transition: var(--transition-fast); cursor: pointer; white-space: nowrap; } .breadcrumb-back:hover { color: var(--accent-gold-dark); } .breadcrumb-separator { margin: 0 0.8rem; color: var(--border-medium); white-space: nowrap; } /* === Layout Components === */ .main { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; min-height: 60vh; padding: var(--spacing-xl) var(--spacing-md) 4rem var(--spacing-md); max-width: 1200px; width: 100%; margin: 0 auto; position: relative; overflow-x: hidden; } .main > * { animation: fadeInUp 0.6s ease-out both; } .main > *:nth-child(1) { animation-delay: 0.05s; } .main > *:nth-child(2) { animation-delay: 0.12s; } .main > *:nth-child(3) { animation-delay: 0.19s; } .main > *:nth-child(4) { animation-delay: 0.26s; } .main > *:nth-child(5) { animation-delay: 0.33s; } .main > *:nth-child(6) { animation-delay: 0.40s; } .main > *:nth-child(7) { animation-delay: 0.47s; } .main.hidden { display: none; } .hero-section { text-align: center; max-width: 1000px; width: 100%; margin: 0 auto; padding: var(--spacing-xxl) var(--spacing-md) var(--spacing-xl) var(--spacing-md); overflow: hidden; } .lmn-center { display: flex; flex-direction: column; align-items: center; justify-content: center; width: 100%; margin-top: 4rem; padding-top: 80px; } /* Typography */ .main-heading { font-size: clamp(1.75rem, 6vw, 4rem); font-weight: 400; color: #1a1a1a; line-height: 1.1; margin: var(--spacing-sm) 0 var(--spacing-xxl) 0; letter-spacing: -0.03em; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); animation: fadeInUp 0.8s ease-out; max-width: 1000px; width: 100%; word-wrap: break-word; overflow-wrap: break-word; } .title { font-size: var(--font-size-xxl); font-weight: 700; margin-bottom: var(--spacing-md); letter-spacing: 0.01rem; text-align: left; color: var(--text-primary); } .subtitle { font-size: clamp(0.95rem, 2.5vw, var(--font-size-lg)); font-weight: 500; font-family: var(--font-family); color: var(--text-secondary); margin-bottom: var(--spacing-xl); margin-left: auto; margin-right: auto; text-align: center; max-width: 700px; width: 100%; line-height: 1.6; animation: fadeInUp 0.8s ease-out 0.2s both; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); word-wrap: break-word; overflow-wrap: break-word; } .lmn-install-title { font-size: var(--font-size-xxxl); font-weight: 400; color: var(--text-primary); letter-spacing: 0.01em; margin: 0.8em 0 0.3em 0; font-family: inherit; text-align: center; display: block; line-height: 1.05; margin: var(--spacing-md) 0 var(--spacing-md) 0; letter-spacing: -0.03em; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); animation: fadeInUp 0.8s ease-out; max-width: 1000px; width: 100%; } /* === Animations === */ @keyframes fadeInUp { from { opacity: 0; transform: translateY(30px); } to { opacity: 1; transform: translateY(0); } } @keyframes shimmer { 0% { transform: translateX(-100%); } 100% { transform: translateX(100%); } } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.8; } } /* === Button Components === */ .btn-base { display: inline-block; font-weight: 700; border: none; border-radius: var(--radius-xl); cursor: pointer; transition: var(--transition-medium); text-decoration: none; outline: none; letter-spacing: 0.01em; position: relative; overflow: hidden; } .btn-base::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 100%; background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); transition: left 0.5s ease; } .btn-base:hover::before { left: 100%; } .download-btn, .dev-btn, .marketplace-cta-btn { display: inline-block; font-weight: 700; border: none; border-radius: var(--radius-xl); cursor: pointer; transition: var(--transition-medium); text-decoration: none; outline: none; letter-spacing: 0.01em; position: relative; overflow: hidden; font-size: 1.1rem; padding: var(--spacing-md) 2.5rem 0.8rem 2.5rem; margin-bottom: var(--spacing-md); display: flex; flex-direction: column; align-items: center; line-height: 1.2; min-width: min(250px, 80vw); max-width: min(250px, 80vw); height: 70px; justify-content: center; } .download-btn { background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 50%, var(--primary-yellow-darker) 100%); color: var(--text-primary); box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1), 0 2px 0 rgba(255, 255, 255, 0.3) inset; } .download-btn:hover, .download-btn:focus { box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), 0 2px 0 rgba(255, 255, 255, 0.4) inset; transform: translateY(-2px) scale(1.02); } .download-btn:active { background: var(--primary-yellow); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transform: scale(0.98); } .dev-btn, .marketplace-cta-btn { background: linear-gradient(135deg, var(--bg-white) 0%, #f8f9fa 100%); color: #333; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), 0 2px 0 rgba(255, 255, 255, 0.6) inset; } .dev-btn:hover, .dev-btn:focus { background: linear-gradient(135deg, #f8f9fa 0%, var(--bg-white) 100%); box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), 0 2px 0 rgba(255, 255, 255, 0.6) inset; transform: translateY(-2px) scale(1.02); } .marketplace-cta-btn:hover, .marketplace-cta-btn:focus { transform: none; } .dev-btn:active { background: #ededed; box-shadow: 0 2px 8px rgba(187, 187, 187, 0.13); border-color: #888; transform: scale(0.98); } /* Marketplace button - single line variant */ .marketplace-cta-btn { display: inline-block; min-width: auto; max-width: none; height: auto; padding: var(--spacing-md) 2rem; font-size: 1rem; flex-direction: unset; align-items: unset; justify-content: unset; } .download-btn .download-sub, .dev-btn .dev-sub { font-size: var(--font-size-sm); color: #3d4000; font-weight: 400; opacity: 0.7; margin-top: 0.25em; letter-spacing: 0.01em; font-style: italic; text-decoration: none; } .button-row { display: flex; flex-direction: row; justify-content: center; align-items: center; gap: var(--spacing-lg); margin-bottom: var(--spacing-sm); animation: fadeInUp 0.8s ease-out 0.2s both; } .lmn-btn { display: inline-block; background: linear-gradient(90deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 100%); color: var(--text-primary) !important; font-weight: 700; font-size: 1.18rem; border: none; border-radius: var(--radius-xl); padding: 1.1em 2.5em 0.9em 2.5em; margin: 0.2em 0.2em 0.2em 0; text-decoration: none !important; box-shadow: var(--shadow-light), 0 1.5px 0 var(--bg-primary) inset; transition: var(--transition-fast); cursor: pointer; outline: none; letter-spacing: 0.01em; min-width: 150px; } .lmn-btn:hover, .lmn-btn:focus { box-shadow: var(--shadow-medium), 0 1.5px 0 var(--bg-primary) inset; transform: translateY(-1px) scale(1.01); } .lmn-btn:active { background: var(--primary-yellow); box-shadow: var(--shadow-light); transform: scale(0.98); } .lmn-btn:visited { color: var(--text-primary); } /* === Community Section === */ .community-section { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: var(--spacing-lg); margin: 0 auto; max-width: 1000px; width: 100%; padding: var(--spacing-sm) var(--spacing-md) 2rem var(--spacing-md); animation: fadeInUp 0.8s ease-out 0.5s both; position: relative; overflow: hidden; } .community-section .subtitle { margin-bottom: var(--spacing-sm); text-align: center; } .community-buttons { display: flex; justify-content: center; align-items: center; gap: var(--spacing-lg); flex-wrap: wrap; } .community-buttons a { display: inline-block; transition: var(--transition-medium); opacity: 0.9; } .community-buttons a:hover { transform: translateY(-2px); opacity: 1; } .community-buttons img { height: 32px; transition: var(--transition-medium); border-radius: var(--radius-sm); } /* === Content Sections === */ .partner-apps { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: var(--spacing-lg); margin: 0 auto; padding: var(--spacing-xl) 0; max-width: 1000px; width: 100%; padding: var(--spacing-lg) var(--spacing-lg); /* background: transparent; */ animation: fadeInUp 0.8s ease-out 0.6s both; position: relative; } .partner-apps .subtitle { margin-bottom: var(--spacing-sm); text-align: center; } .partner-apps-images { display: flex; justify-content: center; align-items: center; gap: clamp(1rem, 4vw, 2.5rem); flex-wrap: wrap; max-width: 100%; padding: 0 var(--spacing-sm); } .partner-apps a { display: inline-block; transition: var(--transition-slow); opacity: 0.85; position: relative; } .partner-apps a:hover::before { width: 120px; height: 120px; } .partner-apps a:hover:not(.marketplace-cta-btn) { transform: translateY(-6px) scale(1.05); opacity: 1; box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2); } .partner-apps img { width: 56px; height: 56px; object-fit: contain; border-radius: var(--radius-md); transition: var(--transition-medium); } .partner-apps a:hover img { transform: scale(1.05); } .partner-apps-link { margin-top: var(--spacing-sm); text-align: center; } .view-all-link { display: inline-block; color: var(--accent-gold); font-size: 0.95rem; font-weight: 600; text-decoration: none; padding: 10px 20px; border: 2px solid var(--accent-gold); border-radius: var(--radius-lg); transition: var(--transition-medium); } .view-all-link:hover { background: var(--accent-gold); color: var(--text-primary); transform: translateY(-2px); } /* Video Section */ .video-section { margin: var(--spacing-xxl) auto var(--spacing-xl) auto; max-width: 1000px; width: 100%; padding: 0 var(--spacing-md); position: relative; } .video-container { position: relative; width: 100%; height: 0; padding-bottom: 56.25%; /* 16:9 aspect ratio */ background: linear-gradient(45deg, #000 0%, #1a1a1a 100%); border-radius: var(--radius-xxl); overflow: hidden; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.1); transition: var(--transition-slow); border: 1px solid rgba(255, 255, 255, 0.1); } .video-container:hover { transform: translateY(-8px) scale(1.02); box-shadow: 0 30px 80px rgba(0, 0, 0, 0.4), 0 12px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.2); } .video-container iframe { position: absolute; top: 0; left: 0; width: 100%; height: 100%; border: none; border-radius: var(--radius-xxl); } /* Features Section */ .features-section { margin: var(--spacing-xl) auto; max-width: 1100px; width: 100%; padding: var(--spacing-xl); background: rgba(255, 255, 255, 0.6); border-radius: 24px; border: 1px solid rgba(255, 255, 255, 0.2); box-shadow: var(--shadow-heavy); backdrop-filter: blur(8px); overflow: hidden; } .features-grid { display: grid; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, 1fr); gap: var(--spacing-md); margin: 0 auto; } .feature-box { background: transparent; border-radius: 12px; padding: var(--spacing-md); text-align: center; border: 1px solid rgba(0, 0, 0, 0.06); display: flex; flex-direction: column; align-items: center; justify-content: flex-start; transition: var(--transition-medium); } .feature-box:hover { background: rgba(255, 255, 255, 0.5); position: relative; } .feature-icon { width: 56px; height: 56px; margin-bottom: var(--spacing-md); display: flex; align-items: center; justify-content: center; } .feature-icon img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; } .feature-title { font-size: var(--font-size-base); font-weight: 600; color: var(--text-primary); margin-bottom: var(--spacing-sm); line-height: 1.3; min-height: 2.6em; /* Reserve space for 2 lines */ display: flex; align-items: center; justify-content: center; } .feature-subtitle { font-size: var(--font-size-sm); color: var(--text-secondary); line-height: 1.5; margin: 0; } /* Responsive design for features */ @media (max-width: 1024px) { .features-section { margin: var(--spacing-xl) var(--spacing-md); padding: var(--spacing-lg); } .features-grid { grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(4, 1fr); gap: var(--spacing-md); } } @media (max-width: 768px) { .features-section { padding: var(--spacing-md); } .features-grid { grid-template-columns: 1fr; grid-template-rows: repeat(8, 1fr); gap: var(--spacing-sm); } .feature-box { padding: var(--spacing-md); } } /* Release Announcement */ .release-announcement { margin: var(--spacing-xl) auto; max-width: 1000px; background: rgba(255, 255, 255, 0.6); border-radius: 20px; padding: 0 var(--spacing-md); box-shadow: var(--shadow-heavy); backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.2); animation: fadeInUp 0.8s ease-out 0.8s both; position: relative; overflow: hidden; display: flex; flex-direction: column; } .release-announcement::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(45deg, transparent 30%, rgba(255, 224, 102, 0.05) 50%, transparent 70%); border-radius: 20px; pointer-events: none; } .release-content { display: flex; align-items: flex-start; gap: var(--spacing-lg); flex-grow: 1; margin-bottom: var(--spacing-lg); padding: var(--spacing-md) var(--spacing-md) 1rem var(--spacing-md); } .release-icon { font-size: var(--spacing-xl); flex-shrink: 0; } .release-info { flex-grow: 1; text-align: left; } .release-title { font-size: 1.1rem; font-weight: 600; color: #000000; margin: 0 0 var(--spacing-sm) 0; text-transform: uppercase; letter-spacing: 0.5px; } .release-name { font-size: 1.3rem; font-weight: 700; color: #333; margin: 0 0 0.3rem 0; line-height: 1.2; } .release-date { font-size: 0.9rem; color: var(--text-muted); margin: 0 0 0.8rem 0; font-weight: 500; } .release-highlights { margin-top: var(--spacing-sm); } .highlight-item { font-size: 0.9rem; color: var(--text-secondary); margin: 0.4rem 0; padding: 0.3rem 0.8rem; border-left: 3px solid var(--accent-gold); border-radius: 0 var(--radius-sm) var(--radius-sm) 0; line-height: 1.3; } .release-link { background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 50%, var(--primary-yellow-darker) 100%); color: var(--text-primary); text-decoration: none; padding: var(--spacing-md) 2rem; border-radius: var(--radius-xl); font-weight: 700; font-size: var(--font-size-base); padding: 0.8rem var(--spacing-lg); transition: var(--transition-medium); box-shadow: 0 6px 20px rgba(255, 224, 102, 0.4), 0 2px 0 rgba(255, 255, 255, 0.3) inset; align-self: flex-start; margin: auto var(--spacing-md) var(--spacing-md) var(--spacing-md); display: inline-block; cursor: pointer; outline: none; letter-spacing: 0.01em; position: relative; overflow: hidden; min-width: 140px; text-align: center; } .release-link:hover { transform: translateY(-2px) scale(1.02); box-shadow: 0 8px 25px rgba(255, 224, 102, 0.5), 0 2px 0 rgba(255, 255, 255, 0.4) inset; } .release-link:active { background: var(--primary-yellow); box-shadow: 0 2px 8px rgba(255, 224, 102, 0.33); transform: scale(0.98); } /* News Banner */ .news-ticker-outer { min-height: 40px; display: flex; align-items: center; overflow: hidden; width: 100%; } .news-ticker-track { display: flex; align-items: center; white-space: nowrap; will-change: transform; animation: ticker-scroll 30s linear infinite; } .news-ticker-outer:hover .news-ticker-track { animation-play-state: paused; } .news-banner { max-width: 1000px; width: 100%; margin: var(--spacing-md) auto; overflow: hidden; min-height: 48px; padding: 1.2rem 2rem; } .news-banner-content { display: flex; align-items: center; gap: var(--spacing-md); overflow: hidden; position: relative; } .news-label { font-size: var(--font-size-sm); font-weight: 700; color: var(--accent-gold-dark); letter-spacing: 0.05em; display: flex; align-items: center; gap: 0.5em; } .news-item { flex-grow: 1; min-width: 0; overflow: hidden; position: relative; height: 40px; display: flex; align-items: center; } .news-ticker-container { display: flex; align-items: center; white-space: nowrap; will-change: transform; animation: scroll-banner 40s linear infinite; position: absolute; top: 0; left: 0; height: 40px; } .news-ticker-item, .news-title { font-size: var(--font-size-base); font-weight: 600; color: var(--text-primary); text-decoration: none; white-space: nowrap; transition: var(--transition-medium); display: inline-flex; align-items: center; height: 40px; } .news-ticker-item { margin-right: 8px; margin-left: 8px; } .news-ticker-item:hover, .news-title:hover { color: var(--accent-gold-dark); text-decoration: underline; } .news-ticker-separator { font-size: var(--font-size-base); color: var(--text-primary); margin: 0 8px; display: inline-flex; align-items: center; height: 40px; } .news-ticker-container:hover { animation-play-state: paused; } /* Keyframes */ @keyframes ticker-scroll { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } @keyframes scroll-banner { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } } @keyframes scroll-horizontal { 0% { left: 100%; } 100% { left: -100%; } } /* Logo Ticker */ .logo-ticker-outer { width: 100%; max-width: 100vw; overflow: hidden; margin: var(--spacing-sm) 0; } .logo-ticker-track { display: flex; align-items: center; gap: 2.5rem; white-space: nowrap; will-change: transform; } .logo-ticker-track a { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; height: clamp(40px, 10vw, 60px); } .logo-ticker-track img { height: clamp(40px, 10vw, 60px); width: auto; object-fit: contain; } /* Responsive Design */ @media (max-width: 600px) { .news-ticker-container { animation-duration: 25s; } } @media (max-width: 768px) { .news-banner-content { flex-direction: column; align-items: flex-start; gap: 0.5em; padding: 4rem 1rem; } .news-label { margin-bottom: 0.3em; } .news-ticker-container { animation: scroll-horizontal 15s linear infinite; } .news-title:not(.news-ticker) { animation: none; white-space: normal; overflow: visible; position: static; } } /* === Installer Components === */ .lmn-installer-table { border-collapse: separate; border-spacing: 0; margin: 0.5em 0; font-family: inherit; color: var(--text-primary); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08); border-radius: var(--radius-lg); overflow: hidden; background: var(--bg-white); table-layout: fixed; width: 100%; max-width: 650px; position: relative; } .lmn-installer-table::before { content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: linear-gradient(45deg, transparent 30%, rgba(255, 224, 102, 0.02) 50%, transparent 70%); pointer-events: none; border-radius: var(--radius-lg); } .lmn-installer-table td, .lmn-installer-table th { border: 1px solid rgba(255, 224, 102, 0.4); text-align: center; font-size: var(--font-size-base); background: var(--bg-white); min-width: 120px; height: 60px; vertical-align: middle; transition: var(--transition-medium); cursor: pointer; font-weight: 500; position: relative; overflow: hidden; line-height: 1.4; } .lmn-installer-table td:hover:not(.lmn-label):not(.lmn-active) { background: rgba(255, 224, 102, 0.1); transform: translateY(-1px); box-shadow: 0 2px 8px rgba(255, 224, 102, 0.2); } .lmn-installer-table tr:first-child td:first-child, .lmn-installer-table tr:first-child th:first-child { border-top-left-radius: var(--radius-lg); } .lmn-installer-table tr:first-child td:last-child, .lmn-installer-table tr:first-child th:last-child { border-top-right-radius: var(--radius-lg); } .lmn-installer-table tr:last-child td:first-child, .lmn-installer-table tr:last-child th:first-child { border-bottom-left-radius: var(--radius-lg); } .lmn-installer-table tr:last-child td:last-child, .lmn-installer-table tr:last-child th:last-child { border-bottom-right-radius: var(--radius-lg); } .lmn-badge { display: inline-block; background: #007acc; color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; margin-right: 8px; margin-bottom: 4px; font-weight: 500; } .lmn-badges { display: flex; flex-wrap: wrap; gap: 0.5em; justify-content: flex-start; margin-bottom: 1.2em; } /* Ensure active state is properly defined */ .lmn-installer-table .lmn-active { background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 100%) !important; font-weight: 600 !important; color: var(--text-primary) !important; box-shadow: 0 2px 8px rgba(255, 224, 102, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.3) !important; border-color: var(--primary-yellow-darker) !important; } /* Ensure disabled state works */ .lmn-installer-table .lmn-disabled { background: #f5f5f5 !important; color: #616161 !important; opacity: 0.6; } /* Label cells styling */ .lmn-installer-table .lmn-label { background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%) !important; font-weight: 600; color: var(--text-primary); cursor: default !important; border-right: 3px solid var(--primary-yellow-darker) !important; text-transform: uppercase; font-size: 0.9rem; letter-spacing: 0.5px; } /* Copy button improvements */ .lmn-copy-btn { display: none; position: absolute; right: 0.7em; top: 50%; transform: translateY(-50%); background: var(--primary-yellow); border: none; border-radius: 6px; color: var(--text-primary); font-size: 0.9rem; padding: 0.3em 0.6em; cursor: pointer; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); transition: var(--transition-fast); z-index: 10; font-weight: 500; } .lmn-command-line:hover .lmn-copy-btn { display: inline-block; } .lmn-copy-btn:hover { background: var(--primary-yellow-dark); transform: translateY(-50%) scale(1.05); } /* Content section improvements */ .lmn-content-section { background: var(--bg-white); border-radius: var(--radius-lg); padding: var(--spacing-lg); margin-top: var(--spacing-lg); box-shadow: var(--shadow-light); max-width: 650px; width: 100%; box-sizing: border-box; } /* Download section styling */ .lmn-download-section { border-radius: var(--radius-lg); padding: 0.5em 0 1em 0; margin-bottom: 0.8em; text-align: center; } .lmn-download-section a { color: var(--text-primary); text-decoration: none; font-weight: 600; font-size: 1.1rem; display: inline-block; padding: var(--spacing-md) var(--spacing-xl); background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 100%); border-radius: var(--radius-md); transition: var(--transition-medium); border: 1px solid rgba(230, 184, 0, 0.3); box-shadow: 0 4px 16px rgba(255, 224, 102, 0.4); min-width: 250px; } .lmn-download-section a:hover { background: linear-gradient(135deg, var(--primary-yellow-dark) 0%, var(--primary-yellow-darker) 100%); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 224, 102, 0.5); } .lmn-command-area { background: var(--bg-primary); border: none; border-radius: var(--radius-xl); margin-top: 0.8em; padding: 0.8em var(--spacing-md); box-shadow: none; max-width: 600px; width: 100%; font-family: inherit; display: flex; flex-direction: column; align-items: center; } .lmn-command-area b { font-size: 1.13rem; color: var(--text-primary); font-weight: 600; margin: 0 0 1em 0; padding-bottom: 0.5em; border-bottom: 2px solid #ffe066; display: flex; align-items: center; gap: 0.5em; } .lmn-section-header::before { content: "⚡"; font-size: 1.1em; } .lmn-explore-header::before { content: "🚀"; } .lmn-badges { display: flex; flex-wrap: wrap; gap: 0.5em; justify-content: flex-start; margin-bottom: 1.2em; } .lmn-badges img { border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.2s ease; } .lmn-badges img:hover { transform: translateY(-1px); } /* Command styling improvements */ .lmn-command pre { background: var(--text-primary); color: var(--bg-white); border-radius: 10px; padding: 0.8em 4.5em 0.8em var(--spacing-md); font-size: 1.08rem; margin: 0.5em 0 0 0; font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', 'monospace'; overflow-x: auto; box-shadow: 0 2px 8px rgba(255, 224, 102, 0.2); width: 100%; min-width: 0; max-width: 100%; position: relative; } .lmn-download-section { border-radius: 12px; padding: 0.5em 0 1em 0; margin-bottom: 0.8em; text-align: center; } .lmn-download-section a { color: #222; text-decoration: none; font-weight: 600; font-size: 1.15rem; display: inline-block; padding: 1em 2em; background: linear-gradient(135deg, #ffe066 0%, #ffd43b 100%); border-radius: 10px; transition: all 0.2s ease; border: 1px solid rgba(230, 184, 0, 0.3); box-shadow: 0 4px 16px rgba(255, 224, 102, 0.4); min-width: 280px; line-height: 1.3; } .lmn-download-section a:hover { background: linear-gradient(135deg, #ffd43b 0%, #ffcc00 100%); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 224, 102, 0.5); } .lmn-command code { background: none; color: inherit; font-size: inherit; font-family: inherit; padding: 0; display: block; width: 100%; box-sizing: border-box; } .lmn-command a { color: var(--accent-gold); text-decoration: underline; font-size: 1.08rem; } .lmn-command a:hover { color: var(--accent-gold-dark); } .lmn-command-line { display: flex; align-items: center; position: relative; padding-right: 0; min-width: 0; word-break: break-all; width: calc(100% + 5.7em); margin-left: -1.2em; padding-left: 1.2em; } .lmn-command-line span { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; padding-right: 0.5em; max-width: calc(100% - 5.7em); } .lmn-copy-btn { display: none; position: absolute; right: 0.7em; top: 50%; transform: translateY(-50%); background: var(--primary-yellow); border: none; border-radius: 0.7em; color: var(--text-primary); font-size: 1.02rem; padding: 0.2em 0.8em; cursor: pointer; box-shadow: 0 2px 8px rgba(255, 224, 102, 0.2); transition: var(--transition-fast); z-index: 2; flex-shrink: 0; } .lmn-command-line:hover .lmn-copy-btn { display: inline-block; } .lmn-copy-btn:hover { transform: translateY(-50%) scale(1.05); } .lmn-copy-btn:active { background: var(--primary-yellow-dark); } .lmn-install-view { display: none; flex-direction: column; align-items: center; justify-content: flex-start; width: 100%; margin-top: var(--spacing-md); padding: 0 var(--spacing-xl); } .lmn-installer-container { margin-top: 0.8em; width: 100%; display: flex; flex-direction: column; align-items: center; } /* === Footer === */ .site-footer { width: 100%; padding: calc(var(--spacing-xxl) / 8) 0 var(--spacing-lg) 0; margin: calc(var(--spacing-xxl) / 8) auto 0 auto; background-color: transparent; border-top: 1px solid #f3f4f6; position: relative; overflow: hidden; } .footer-content { max-width: 1160px; margin: 0 auto; padding: 0 var(--spacing-lg); display: block; margin-bottom: var(--spacing-md); } .footer-nav-rail { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 0.9rem 1.15rem; padding: 0.1rem 0 0.85rem; border-bottom: 1px solid rgba(0, 0, 0, 0.09); } .footer-rail-link { text-decoration: none; display: inline-flex; align-items: center; justify-content: center; gap: 0.44rem; min-height: 24px; padding: 0.2rem 0; position: relative; color: #3a4251; font-size: 0.95rem; font-weight: 650; letter-spacing: 0.005em; transition: color 0.2s ease; } .footer-rail-link::after { content: ""; position: absolute; left: 0; right: 0; bottom: -0.26rem; height: 2px; border-radius: 999px; background: currentColor; opacity: 0; transform: scaleX(0.35); transform-origin: center; transition: transform 0.2s ease, opacity 0.2s ease; } .footer-rail-link:hover::after, .footer-rail-link:focus::after { opacity: 0.42; transform: scaleX(1); } .footer-rail-link--community { color: #1f2438; } .footer-rail-link--community:hover, .footer-rail-link--community:focus { color: #111827; } .footer-rail-link--product { color: #1f2438; } .footer-rail-link--product:hover, .footer-rail-link--product:focus { color: #111827; } .footer-rail-icon { width: 15px; height: 15px; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; line-height: 0; } .footer-rail-icon img { width: 100%; height: 100%; object-fit: contain; } .footer-meta { margin-top: 0.5rem; display: flex; justify-content: center; } .copyright { font-size: 0.95rem; color: #6b7280; margin: 0; font-weight: 400; } /* Footer responsive design */ @media (min-width: 1024px) { } @media (max-width: 1023px) { .footer-content { padding: 0 var(--spacing-md); } } @media (max-width: 768px) { .site-footer { padding: var(--spacing-xl) 0 var(--spacing-lg) 0; } .footer-content { padding: 0 var(--spacing-md); } .footer-nav-rail { gap: 0.72rem 0.9rem; padding-bottom: 0.72rem; } } @media (max-width: 640px) { .footer-content { padding: 0 var(--spacing-lg); } } @media (max-width: 480px) { .site-footer { padding: var(--spacing-lg) 0; } .footer-content { padding: 0 var(--spacing-md); } .footer-nav-rail { gap: 0.6rem 0.72rem; } .footer-rail-link { font-size: 0.92rem; } .copyright { font-size: 0.85rem; } } /* === News Components === */ .news-sidebar { position: fixed; top: 0; left: 0; width: 250px; height: 100vh; background: var(--bg-primary); border-right: 2px solid var(--border-light); box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1); z-index: 150; padding: var(--spacing-xl) var(--spacing-lg); overflow-y: auto; transition: var(--transition-medium); } .news-sidebar.collapsed { transform: translateX(-250px); } .sidebar-header { margin-bottom: var(--spacing-xl); } .sidebar-header h3 { font-size: var(--font-size-lg); font-weight: 600; color: #333; margin-bottom: var(--spacing-sm); margin-top: 2.5rem; } .sidebar-header p { font-size: 0.9rem; color: var(--text-muted); margin: 0; } .filter-container { display: flex; flex-direction: column; gap: 0.8rem; margin-top: 5rem; /* Prevents filter buttons from sitting behind navbar */ margin-bottom: var(--spacing-xl); } .filter-btn { background: var(--bg-white); border: 2px solid var(--border-light); border-radius: var(--radius-md); padding: 0.8rem 1.2rem; font-size: 0.95rem; font-weight: 500; color: #333; cursor: pointer; transition: var(--transition-medium); display: flex; align-items: center; gap: var(--spacing-sm); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); text-align: left; width: 100%; } .filter-btn:hover { background: #f9f9f9; border-color: var(--border-medium); transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); } .filter-btn.active { background: linear-gradient(90deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 100%); border-color: var(--accent-gold); color: var(--text-primary); font-weight: 600; box-shadow: var(--shadow-medium); } .filter-btn.active:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(230, 184, 0, 0.4); } .sort-container { display: flex; flex-direction: column; gap: var(--spacing-sm); margin-bottom: var(--spacing-xl); } .sort-label { font-size: 0.9rem; font-weight: 600; color: #333; } .sort-select { background: var(--bg-white); border: 2px solid var(--border-light); border-radius: var(--radius-md); padding: 0.6rem 0.8rem; font-size: 0.9rem; color: #333; cursor: pointer; transition: var(--transition-medium); outline: none; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); width: 100%; } .sort-select:hover { border-color: var(--border-medium); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); } .sort-select:focus { border-color: var(--accent-gold); box-shadow: var(--shadow-medium); } .news-view { padding: var(--spacing-xl) var(--spacing-xl) var(--spacing-xl) 270px; max-width: 1200px; margin: 0 auto; flex-grow: 1; transition: var(--transition-medium); } .news-view.sidebar-collapsed { padding-left: 50px; } .news-view .hero-section { text-align: center; max-width: 1000px; margin: 0 auto var(--spacing-xl) auto; padding: var(--spacing-md); } .news-view .main-heading { font-size: 3.5rem; font-weight: 400; color: #1a1a1a; line-height: 1.05; margin: var(--spacing-sm) 0 var(--spacing-md) 0; letter-spacing: -0.03em; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); animation: fadeInUp 0.8s ease-out; max-width: 1000px; width: 100%; } .news-view .subtitle { font-size: var(--font-size-lg); font-weight: 500; font-family: var(--font-family); color: var(--text-secondary); margin-bottom: var(--spacing-xxl); text-align: center; max-width: 700px; margin-left: auto; margin-right: auto; line-height: 1.6; animation: fadeInUp 0.8s ease-out 0.2s both; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .news-posts { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: var(--spacing-xl); margin-bottom: var(--spacing-xl); } .news-tile { background: var(--bg-white); border-radius: var(--radius-lg); overflow: hidden; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transition: var(--transition-medium); aspect-ratio: 0.8; display: flex; flex-direction: column; position: relative; width: 100%; } .news-tile:hover { transform: translateY(-4px); box-shadow: var(--shadow-heavy); } .news-image { height: 40%; background: linear-gradient(135deg, #f4d03f 0%, #f7dc6f 100%); display: flex; align-items: center; justify-content: center; position: relative; } .news-image-placeholder { font-size: var(--spacing-xxl); opacity: 0.8; } .news-image-actual { width: 120%; height: 120%; object-fit: cover; object-position: center; transform: scale(1.1); } .news-content { padding: var(--spacing-lg); flex-grow: 1; display: flex; flex-direction: column; } .news-content h3 { margin: 0 0 var(--spacing-md) 0; color: #333; font-size: var(--font-size-lg); line-height: 1.3; font-weight: 600; } .news-date { color: var(--text-muted); font-size: 0.9rem; font-weight: 500; margin-bottom: 10px; } .news-content p { color: var(--text-secondary); font-size: 0.9rem; line-height: 1.4; margin: 0; flex-grow: 1; display: block; overflow: visible; text-overflow: unset; } .news-tile-link { text-decoration: none; color: inherit; display: flex; flex-direction: column; height: 100%; } .news-tile-link:hover { text-decoration: none; color: inherit; } .youtube-play-button { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.8); color: white; border-radius: var(--radius-round); width: 60px; height: 60px; display: flex; align-items: center; justify-content: center; font-size: 20px; transition: var(--transition-medium); pointer-events: none; } .news-tile:hover .youtube-play-button { background: rgba(255, 0, 0, 0.9); transform: translate(-50%, -50%) scale(1.1); } .content-type-badge { position: absolute; bottom: 12px; right: 12px; padding: 4px 8px; border-radius: 4px; font-size: 0.8rem; font-weight: 500; color: white; z-index: 10; } .blog-badge { background-color: #007acc; } .video-badge { background-color: #dc3545; } .tutorial-badge { background-color: #28a745; } .release-badge { background-color: var(--primary-yellow); } /* News sidebar controls */ .collapse-btn, .expand-btn { background: var(--bg-white); border: 2px solid var(--border-light); border-radius: var(--radius-sm); padding: 0.4rem 0.6rem; font-size: 0.9rem; color: #333; cursor: pointer; transition: var(--transition-medium); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); z-index: 10; } .collapse-btn { position: absolute; top: var(--spacing-md); right: var(--spacing-md); } .expand-btn { position: fixed; top: 4.5rem; left: 1.5rem; background: linear-gradient(90deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 100%); border: none; font-weight: 500; color: var(--text-primary); box-shadow: var(--shadow-light); z-index: 160; opacity: 0; visibility: hidden; transform: translateX(-20px); } .expand-btn.show { opacity: 1; visibility: visible; transform: translateX(0); } .expand-btn:hover { box-shadow: var(--shadow-medium); transform: translateY(-1px); } .sidebar-toggle { display: none; } .sidebar-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 140; opacity: 0; visibility: hidden; transition: var(--transition-medium); } .sidebar-overlay.active { opacity: 1; visibility: visible; } /* === Utility Classes === */ .back-to-top-btn { position: fixed; bottom: 80px; right: 20px; width: 50px; height: 50px; background: linear-gradient(90deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 100%); border: none; border-radius: var(--radius-round); box-shadow: var(--shadow-medium); cursor: pointer; opacity: 0; visibility: hidden; transform: translateY(20px); transition: var(--transition-medium); z-index: 1000; display: flex; align-items: center; justify-content: center; } .back-to-top-btn.show { opacity: 1; visibility: visible; transform: translateY(0); } .back-to-top-btn:hover { box-shadow: 0 6px 20px rgba(230, 184, 0, 0.4); transform: translateY(-2px); } .back-to-top-btn:active { transform: translateY(0); box-shadow: var(--shadow-light); } .back-to-top-icon { font-size: 20px; font-weight: bold; color: var(--text-primary); line-height: 1; } .text-muted { color: var(--border-medium); } .command-margin { margin-top: 0.6em; } /* === Install Selector Components === */ /* Unified content container */ .lmn-content-section { background: #fff; border-radius: 18px; padding: 1.5em; margin-top: 1.2em; box-shadow: 0 4px 24px #ffe06633; max-width: 650px; width: 100%; box-sizing: border-box; } .lmn-section-header { font-size: 1.2rem; color: #222; font-weight: 600; margin: 0 0 1em 0; padding-bottom: 0.5em; border-bottom: 2px solid #ffe066; display: flex; align-items: center; gap: 0.5em; } .lmn-section-header::before { content: "⚡"; font-size: 1.1em; } .lmn-install-section-title { font-size: 1.2rem; color: #333; font-weight: 700; margin: 2em 0 1em 0; padding-bottom: 0.5em; border-bottom: 2px solid #e0e0e0; } .lmn-install-section-title:first-child { margin-top: 0; } .lmn-install-method-header { font-size: 1.05rem; color: #555; font-weight: 600; margin: 1.5em 0 0.8em 0; } .lmn-install-method-header:first-child { margin-top: 0; } .lmn-explore-header::before { content: "🚀"; } .lmn-badges { display: flex; flex-wrap: wrap; gap: 0.5em; justify-content: flex-start; margin-bottom: 1.2em; } .lmn-badges img { border-radius: 6px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); transition: transform 0.2s ease; } .lmn-badges img:hover { transform: translateY(-1px); } /* Command styling improvements */ .lmn-command pre { background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%); color: #fff; border-radius: 12px; padding: 1em 4.5em 1em 1.2em; font-size: 1.06rem; margin: 0; font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', 'monospace'; overflow-x: auto; box-shadow: 0 4px 16px rgba(0,0,0,0.15); border: 1px solid rgba(255, 224, 102, 0.2); position: relative; } .lmn-download-section { border-radius: 12px; padding: 0.5em 0 1em 0; margin-bottom: 0.8em; text-align: center; } .lmn-download-section a { color: #222; text-decoration: none; font-weight: 600; font-size: 1.15rem; display: inline-block; padding: 1em 2em; background: linear-gradient(135deg, #ffe066 0%, #ffd43b 100%); border-radius: 10px; transition: all 0.2s ease; border: 1px solid rgba(230, 184, 0, 0.3); box-shadow: 0 4px 16px rgba(255, 224, 102, 0.4); min-width: 280px; line-height: 1.3; } .lmn-download-section a:hover { background: linear-gradient(135deg, #ffd43b 0%, #ffcc00 100%); transform: translateY(-2px); box-shadow: 0 6px 20px rgba(255, 224, 102, 0.5); } .lmn-command code { background: none; color: inherit; font-size: inherit; font-family: inherit; padding: 0; display: block; width: 100%; box-sizing: border-box; } .lmn-command a { color: #e6b800; text-decoration: underline; font-size: 1.08rem; } .lmn-command a:hover { color: #bfa100; } .lmn-note { margin-top: 0.8em; color: #666; font-size: 1.02rem; line-height: 1.5; } .lmn-note a { color: #666; text-decoration: underline; } .lmn-note a:hover { color: #444; } .lmn-note code { font-family: 'Fira Mono', 'Consolas', 'Menlo', 'Monaco', monospace; background: #f5f5f5; padding: 2px 6px; border-radius: 4px; font-size: 0.95em; } .lmn-source-note { margin-top: 0.8em; } .lmn-coming-soon { text-align: center; font-size: 1.3rem; font-weight: 600; color: #666; padding: 2em 1em; background: linear-gradient(135deg, #f5f5f5 0%, #e8e8e8 100%); border-radius: 12px; border: 1px solid #ddd; } .lmn-quickstart-table { margin-top: 1.5em; } .lmn-embedded-table { margin: 0 0 1.2em 0; box-shadow: none; border: 1px solid rgba(255, 224, 102, 0.4); } .lmn-command-line { display: flex; align-items: center; position: relative; padding-right: 0; min-width: 0; word-break: break-all; width: calc(100% + 5.7em); box-sizing: border-box; margin-left: -1.2em; padding-left: 1.2em; } .lmn-command-line span { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; padding-right: 0.5em; max-width: calc(100% - 5.7em); } .lmn-copy-btn { display: none; position: absolute; right: 0.7em; top: 50%; transform: translateY(-50%); background: #ffe066; border: none; border-radius: 0.7em; color: #222; font-size: 1.02rem; padding: 0.2em 0.8em; cursor: pointer; box-shadow: 0 2px 8px rgba(255, 224, 102, 0.4); transition: background 0.18s, color 0.18s, transform 0.18s; z-index: 2; flex-shrink: 0; } .lmn-command-line:hover .lmn-copy-btn { display: inline-block; } .lmn-copy-btn:hover { transform: translateY(-50%) scale(1.05); } .lmn-copy-btn:active { background: #ffd43b; } .lmn-install-view { display: none; flex-direction: column; align-items: center; justify-content: flex-start; width: 100%; margin-top: 1rem; padding: 0 2rem; box-sizing: border-box; } .lmn-installer-container { margin-top: 0.8em; width: 100%; display: flex; flex-direction: column; align-items: center; } /* === Decorative Elements === */ .ai-art { margin: var(--spacing-xxl) auto var(--spacing-lg) auto; display: flex; justify-content: center; align-items: flex-end; min-height: 120px; } /* === Responsive Design === */ @media (max-width: 1024px) { .lmn-install-view { margin-top: var(--spacing-md); } .main { margin-top: 6rem; } .news-posts { grid-template-columns: repeat(auto-fit, minmax(250px, 400px)); gap: var(--spacing-lg); } .news-tile { aspect-ratio: 0.9; } .news-sidebar { width: 220px; padding: var(--spacing-lg) var(--spacing-md); } .news-sidebar.collapsed { transform: translateX(-220px); } .news-view { padding: var(--spacing-lg) var(--spacing-lg) var(--spacing-lg) 240px; } .news-view.sidebar-collapsed { padding-left: 50px; } .back-to-top-btn { bottom: 85px; right: 15px; width: 45px; height: 45px; } .back-to-top-icon { font-size: 18px; } } @media (max-width: 900px) { .news-sidebar { width: 200px; padding: var(--spacing-md) 0.8rem; } .news-sidebar.collapsed { transform: translateX(-200px); } .news-view { padding: var(--spacing-md) var(--spacing-md) var(--spacing-md) 220px; } .news-view.sidebar-collapsed { padding-left: 30px; } .collapse-btn { top: 0.8rem; right: 0.8rem; padding: 0.3rem var(--spacing-sm); font-size: var(--font-size-sm); } .expand-btn { top: 0.8rem; left: 0.8rem; padding: 0.3rem var(--spacing-sm); font-size: var(--font-size-sm); } } @media (max-width: 825px) { .navbar { flex-direction: column; gap: var(--spacing-md); padding: 1rem; } .navbar-links { gap: 1.5rem; font-size: 1.18rem; flex-wrap: wrap; justify-content: center; } .navbar-links a { font-size: 1.18rem; } } @media (max-width: 768px) { .navbar { flex-direction: column; gap: var(--spacing-md); padding: 1rem; } .navbar-links { gap: var(--spacing-lg); font-size: 1.12rem; } .navbar-links a { font-size: 1.12rem; } .navbar-context { position: static; font-size: var(--font-size-sm); order: -1; } .main { margin-top: var(--spacing-xxl); padding: var(--spacing-lg) var(--spacing-sm) 3rem var(--spacing-sm); } .hero-section { padding: var(--spacing-lg) var(--spacing-sm); } .title { font-size: var(--font-size-xxl); } .subtitle { font-size: var(--font-size-base); padding: 0 var(--spacing-sm); } .button-row { flex-direction: column; gap: 1rem; width: 100%; padding: 0 var(--spacing-sm); } .download-btn, .dev-btn { font-size: var(--font-size-base); padding: 0.8rem var(--spacing-lg); min-width: auto; max-width: 100%; width: 100%; } .download-btn .download-sub, .dev-btn .dev-sub { font-size: var(--font-size-sm); } .lmn-install-view { padding: 0 var(--spacing-md); margin-top: var(--spacing-sm); } .lmn-install-title { font-size: var(--spacing-lg); } .lmn-installer-table { font-size: 0.98rem; max-width: 99vw; } .lmn-command-area { max-width: 99vw; } .lmn-btn { font-size: var(--font-size-base); padding: 0.8rem var(--spacing-lg); } .breadcrumb { padding: 0.6rem var(--spacing-md); font-size: var(--font-size-sm); min-height: 50px; } .breadcrumb-separator { margin: 0 var(--spacing-sm); } .lmn-center { padding-top: 70px; } .main-heading { font-size: var(--font-size-xxxl); line-height: 1.2; } .partner-apps { gap: var(--spacing-lg); padding: var(--spacing-xl) var(--spacing-md); } .community-section { padding: var(--spacing-lg) var(--spacing-md); } .community-buttons { flex-direction: column; gap: var(--spacing-md); } .partner-apps img { width: 50px; height: 50px; } .video-section { margin: var(--spacing-xl) auto var(--spacing-xxl) auto; max-width: 100%; } .release-announcement { margin: var(--spacing-xl) var(--spacing-md); max-width: none; } .news-banner { margin: var(--spacing-md) var(--spacing-md); max-width: none; } .news-banner-content { flex-direction: column; gap: var(--spacing-sm); align-items: flex-start; } .news-item { align-items: flex-start; } .news-title { animation: none; /* Disable scrolling on smaller screens */ white-space: normal; overflow: visible; } .release-content { flex-direction: column; gap: var(--spacing-md); margin-bottom: 0; } .release-link { align-self: flex-start; } .back-to-top-btn { bottom: 85px; right: 12px; width: 40px; height: 40px; } .back-to-top-icon { font-size: 16px; } .site-footer { padding: 1.2rem 0; } .footer-content { padding: 0 var(--spacing-md); gap: 0.2rem; } .dad-joke { font-size: var(--font-size-base); } .copyright { font-size: 0.8rem; } /* News responsive styles */ .news-sidebar { transform: translateX(-100%); transition: var(--transition-medium); z-index: 160; width: 250px; padding: var(--spacing-lg); } .news-sidebar.mobile-open { transform: translateX(0); } .news-view { padding: var(--spacing-md); margin-top: var(--spacing-xl); } .news-view.sidebar-collapsed { padding-left: var(--spacing-md); } .news-view .main-heading { font-size: var(--font-size-xxxl); margin: var(--spacing-sm) 0; } .news-view .subtitle { font-size: 1.1rem; margin-bottom: var(--spacing-xl); } .collapse-btn { display: none; } .expand-btn { display: none; } .sidebar-toggle { display: block; position: fixed; top: 80px; right: 20px; z-index: 200; background: linear-gradient(90deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 100%); border: none; border-radius: var(--radius-md); padding: 0.6rem var(--spacing-md); font-size: 0.9rem; font-weight: 500; color: var(--text-primary); cursor: pointer; box-shadow: var(--shadow-light); transition: var(--transition-medium); } .sidebar-toggle:hover { box-shadow: var(--shadow-medium); transform: translateY(-1px); } .news-posts { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--spacing-lg); } .news-header { margin-bottom: var(--spacing-xl); } .news-header h1 { font-size: var(--font-size-xxl); } .news-header p { font-size: var(--font-size-base); } .news-tile { aspect-ratio: 0.9; } .news-content { padding: var(--spacing-md); } .news-content h3 { font-size: var(--font-size-base); line-height: 1.2; } .news-date { font-size: 0.8rem; } .news-content p { font-size: var(--font-size-sm); line-height: 1.3; } .youtube-play-button { width: 45px; height: 45px; font-size: 16px; } } @media (max-width: 600px) { .navbar-links { gap: 1rem; font-size: 1.08rem; flex-wrap: wrap; justify-content: center; } .navbar-links a { font-size: 1.08rem; } .hero-section { padding: var(--spacing-md) var(--spacing-xs); } .main-heading { line-height: 1.15; margin-bottom: var(--spacing-lg); } .button-row { padding: 0; } .release-announcement { margin: var(--spacing-lg) auto; max-width: 95%; padding: var(--spacing-md); } .news-banner { margin: var(--spacing-sm) auto; max-width: 95%; min-height: 40px; } .news-banner-content { flex-direction: column; gap: 0.5rem; align-items: center; text-align: center; } .news-item { align-items: center; } .news-title { animation: none; white-space: normal; overflow: visible; font-size: var(--font-size-sm); } .news-label { font-size: var(--font-size-xs); } .release-content { flex-direction: column; text-align: center; gap: 0.8rem; margin-bottom: 0; } .release-info { text-align: center; } .release-icon { font-size: var(--spacing-lg); } .release-title { font-size: 0.9rem; } .release-name { font-size: var(--font-size-base); } .release-date { font-size: 0.8rem; margin-bottom: var(--spacing-sm); } .highlight-item { font-size: var(--font-size-xs); margin: 0.2rem 0; padding-left: var(--spacing-sm); text-align: left; } .release-link { font-size: var(--font-size-base); padding: 0.8rem var(--spacing-lg); min-width: 120px; align-self: center; } .video-section { margin: var(--spacing-xl) auto var(--spacing-lg) auto; max-width: 95%; } .lmn-install-title { font-size: 1.8rem !important; margin: 0.8em 0 0.4em 0; } .back-to-top-btn { bottom: 80px; right: 10px; width: 35px; height: 35px; } .back-to-top-icon { font-size: 14px; } /* News mobile styles */ .news-sidebar { width: 100%; padding: var(--spacing-md); } .sidebar-toggle { top: 70px; right: 15px; padding: var(--spacing-sm) 0.8rem; font-size: var(--font-size-sm); } .news-view { padding: 0.8rem; margin-top: var(--spacing-lg); } .news-header h1 { font-size: 1.8rem; } .news-header p { font-size: 0.9rem; } .news-posts { grid-template-columns: 1fr; gap: 1.2rem; } .news-tile { aspect-ratio: 1.1; } .news-image { height: 50%; } .news-content { padding: 0.6rem; } .news-content h3 { font-size: 0.9rem; margin-bottom: 0.4rem; line-height: 1.1; } .news-content p { font-size: 0.8rem; line-height: 1.2; margin-bottom: 0.2rem; } .news-date { font-size: var(--font-size-xs); margin-bottom: 0.3rem; } .youtube-play-button { width: 40px; height: 40px; font-size: 14px; } } @media (max-width: 480px) { .navbar { padding: 0.75rem; } .navbar-brand .brand-title { font-size: 1.1rem; } .breadcrumb { padding: var(--spacing-sm) 0.8rem; font-size: 0.8rem; flex-wrap: wrap; min-height: auto; } .breadcrumb-back { font-size: 0.8rem; } .lmn-center { padding-top: 65px; } .lmn-install-title { font-size: var(--spacing-lg) !important; margin: 0.6em 0 0.3em 0; } .main { padding: var(--spacing-md) var(--spacing-xs) 2rem var(--spacing-xs); } .hero-section { padding: var(--spacing-sm) var(--spacing-xs); } .main-heading { margin-bottom: var(--spacing-md); } .download-btn, .dev-btn { font-size: 0.95rem; padding: 0.7rem var(--spacing-md); height: auto; min-height: 60px; } .partner-apps { gap: var(--spacing-md); padding: var(--spacing-lg) var(--spacing-xs); } .partner-apps img { width: 40px; height: 40px; } .community-section { padding: var(--spacing-md) var(--spacing-xs); } .news-view { padding: var(--spacing-sm); margin-top: var(--spacing-md); } .news-view .main-heading { font-size: var(--font-size-xxl); margin: 0.3rem 0; } .news-view .subtitle { font-size: var(--font-size-base); margin-bottom: var(--spacing-lg); } .news-header { margin-bottom: var(--spacing-lg); } .news-header h1 { font-size: 1.6rem; } .news-posts { gap: var(--spacing-md); } .news-tile { aspect-ratio: 1.2; } .news-image { height: 45%; } .news-content { padding: var(--spacing-sm); } .news-content h3 { font-size: 0.8rem; margin-bottom: 0.3rem; line-height: 1.1; } .news-content p { font-size: 0.7rem; line-height: 1.2; margin-bottom: 0.1rem; } .news-date { font-size: 0.65rem; margin-bottom: 0.2rem; } .youtube-play-button { width: 35px; height: 35px; font-size: 12px; } /* Community links responsive styles */ .community-section { padding: var(--spacing-lg) var(--spacing-sm); } .community-buttons { flex-direction: column; gap: var(--spacing-md); } .community-stats { flex-direction: column; gap: var(--spacing-md); } .community-stat-card { min-width: 260px; max-width: 300px; padding: var(--spacing-md); } .stat-icon { width: 35px; height: 35px; font-size: 1.3rem; } .badges-section { flex-direction: column; gap: var(--spacing-md); margin: var(--spacing-xl) 0 var(--spacing-lg) 0; } .badge { min-width: 100px; padding: var(--spacing-xs) var(--spacing-sm); font-size: 0.8rem; } } /* === Blog Article Styles === */ .blog-main { max-width: none; padding-top: 0; } .blog-article { width: 100%; max-width: 1120px; margin: 0 auto 3rem; padding: 1.5rem; } .blog-article .hero-section { background: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.58)); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 24px; box-shadow: 0 12px 28px rgba(0, 0, 0, 0.05); padding: 3rem 2rem 2.4rem; margin-bottom: 1.5rem; } .hero-content { text-align: center; max-width: 820px; margin: 0 auto; } .hero-badge { display: inline-block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent-gold-dark); background: rgba(255, 224, 102, 0.2); padding: 0.36rem 0.95rem; border-radius: 100px; margin-bottom: 1rem; } .hero-title { font-size: clamp(2rem, 4.2vw, 3.3rem); font-weight: 760; letter-spacing: -0.03em; line-height: 1.08; margin: 0 0 1rem 0; color: var(--text-primary); } .hero-subtitle { margin: 0 auto; max-width: 760px; font-size: clamp(0.96rem, 1.65vw, 1.08rem); line-height: 1.62; color: #505050; } .hero-meta { display: flex; justify-content: center; gap: 0.55rem; flex-wrap: wrap; margin-top: 1.4rem; max-width: 860px; margin-left: auto; margin-right: auto; } .meta-item { font-size: 0.82rem; font-weight: 600; color: #4b4b4b; background: rgba(255, 255, 255, 0.86); border: 1px solid rgba(0, 0, 0, 0.08); border-radius: 999px; padding: 0.38rem 0.8rem; } .meta-item .meta-icon { color: var(--accent-gold-dark); margin-right: 0.32rem; } .meta-item.meta-authors { flex: 1 1 100%; text-align: center; background: transparent; border-color: transparent; color: #636363; padding-top: 0.15rem; } .article-content { max-width: 860px; margin: 0 auto; font-size: var(--font-size-base); line-height: 1.65; color: #2d2d2d; } .section-card { background: rgba(255, 255, 255, 0.48); border: 1px solid rgba(0, 0, 0, 0.05); border-radius: 20px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.025); padding: 1.8rem; margin: 1.35rem 0; } .section-card + .section-card { margin-top: 2.1rem; position: relative; } .section-card + .section-card::before { content: ""; width: 72px; height: 1px; background: rgba(0, 0, 0, 0.1); position: absolute; top: -1.08rem; left: 50%; transform: translateX(-50%); } /* Unified news article layout */ .blog-article-unified .section-card { background: transparent; border: 0; border-radius: 0; box-shadow: none; padding: 1.2rem 0; margin: 0; } .blog-article-unified { margin-top: 1rem; } .blog-article-unified .hero-section { margin-bottom: 2.25rem; } .blog-article-unified .section-header { text-align: left; margin: 0 0 1.15rem; } .blog-article-unified .section-header .section-kicker { margin-bottom: 0.85rem; } .blog-article-unified .section-header .section-title { font-size: clamp(1.65rem, 3.2vw, 2.5rem); font-weight: 750; letter-spacing: -0.03em; line-height: 1.15; margin: 0 0 0.9rem; } .blog-article-unified .section-header p { font-size: clamp(0.96rem, 1.65vw, 1.08rem); color: #505050; margin: 0; max-width: 760px; line-height: 1.62; } .blog-article-unified .section-card + .section-card { margin-top: 2.2rem; padding-top: 2.1rem; } .blog-article-unified .section-card + .section-card::before { content: none; } .blog-article-unified .model-card-enhanced { box-shadow: none; } .blog-article-unified .model-showcase-single { grid-template-columns: minmax(0, 760px); } .blog-article-arcade .model-showcase-single { grid-template-columns: minmax(0, 1fr); } .blog-article-arcade .model-card-enhanced { max-width: 100%; padding: 1.25rem; display: flex; flex-direction: column; gap: 0.6rem; } .blog-article-arcade .model-card-enhanced > p { margin: 0; } .blog-article-arcade .model-card-enhanced .model-title { margin: 0 0 0.35rem; } .blog-article-arcade .model-stat-grid { margin-top: 0.2rem; margin-bottom: 0.15rem; border-top: 0; padding-top: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.6rem; } .blog-article-arcade .model-stat-row { display: flex; flex-direction: column; align-items: flex-start; justify-content: flex-start; gap: 0.25rem; border: 1px solid rgba(0, 0, 0, 0.08); background: rgba(255, 255, 255, 0.55); border-radius: 10px; padding: 0.6rem 0.7rem; } .blog-article-arcade .model-stat-row strong { color: #2d2d2d; } .blog-article-arcade .arcade-demo-media { width: 50%; margin-left: auto; margin-right: auto; } .blog-article-unified .blog-callout { margin-top: 1.15rem; background: rgba(255, 255, 255, 0.8); border: 1px solid rgba(230, 184, 0, 0.36); border-left: 4px solid var(--accent-gold); border-radius: 12px; padding: 0.95rem 1rem 0.95rem 0.9rem; box-shadow: none; } .blog-article-unified .blog-callout-title { color: #3f3200; } .blog-article-unified .blog-callout p { color: #3f3f3f; } .blog-article-unified .blog-download-table { background: rgba(255, 255, 255, 0.86); border-color: rgba(0, 0, 0, 0.08); } .blog-article-unified .blog-download-table thead th { background: rgba(255, 255, 255, 0.96); color: #3b3b3b; border-bottom: 1px solid rgba(0, 0, 0, 0.08); } .blog-article-unified .cta-separator { margin-top: 2.4rem; padding-top: 1.8rem; border-top: 1px solid rgba(0, 0, 0, 0.08); } .blog-article-unified .cta-section-minimal { margin-top: 3.5rem; background: linear-gradient(180deg, rgba(255, 255, 255, 0.9), rgba(255, 255, 255, 0.74)); border-color: rgba(0, 0, 0, 0.08); } .section-kicker { display: inline-block; margin-bottom: 0.75rem; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent-gold-dark); background: rgba(255, 224, 102, 0.2); padding: 0.34rem 0.9rem; border-radius: 100px; } .section-title { font-size: clamp(1.5rem, 2.3vw, 2rem); font-weight: 740; letter-spacing: -0.02em; line-height: 1.2; margin: 0 0 0.95rem 0; color: #1f1f1f; } .article-content h3 { margin: 1.3rem 0 0.65rem; font-size: 1.2rem; color: #222; } .article-content p { margin: 0 0 1rem 0; } .blog-media { margin: 1.2rem 0; border-radius: 14px; overflow: hidden; border: 1px solid rgba(0, 0, 0, 0.06); box-shadow: 0 10px 24px rgba(0, 0, 0, 0.08); } .blog-media img { display: block; width: 100%; height: auto; } .feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 0.8rem; margin-top: 1.2rem; } .feature-item { background: rgba(255, 255, 255, 0.82); border: 1px solid rgba(0, 0, 0, 0.07); border-radius: 14px; padding: 1rem; } .feature-item-head { display: flex; align-items: center; gap: 0.5rem; font-weight: 700; margin-bottom: 0.5rem; color: #1f1f1f; line-height: 1.35; } .feature-item p { margin: 0; color: #5b5b5b; font-size: 0.94rem; line-height: 1.5; } .feature-icon { display: none; } .feature-icon-image { width: 18px; height: 18px; object-fit: contain; flex-shrink: 0; opacity: 0.95; } .model-showcase { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 0.8rem; margin-top: 1.1rem; } .model-card-enhanced { background: rgba(255, 255, 255, 0.82); border: 1px solid rgba(0, 0, 0, 0.07); border-radius: 16px; padding: 1.15rem; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.04); } .model-title { margin: 0 0 0.7rem; font-size: 1.1rem; font-weight: 700; display: flex; align-items: center; gap: 0.45rem; } .model-badge { font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: #675100; background: rgba(255, 224, 102, 0.34); border-radius: 999px; padding: 0.2rem 0.56rem; } .model-stat-grid { margin-top: 0.85rem; border-top: 1px dashed rgba(0, 0, 0, 0.11); padding-top: 0.7rem; display: grid; gap: 0.4rem; } .model-stat-row { display: flex; justify-content: space-between; gap: 0.6rem; font-size: 0.92rem; color: #555; } .blog-callout { margin-top: 1rem; background: linear-gradient(180deg, rgba(255, 224, 102, 0.22), rgba(255, 224, 102, 0.14)); border: 1px solid rgba(255, 224, 102, 0.48); border-radius: 14px; padding: 0.95rem 1rem; } .blog-callout-title { margin: 0 0 0.4rem; font-size: 0.95rem; font-weight: 700; color: #4d3a00; } .blog-callout p { margin: 0; color: #4f4f4f; } .blog-download-table { width: 100%; border-collapse: separate; border-spacing: 0; margin: 0.85rem 0 0.7rem; background: rgba(255, 255, 255, 0.76); border: 1px solid rgba(0, 0, 0, 0.07); border-radius: 14px; overflow: hidden; } .blog-download-table th, .blog-download-table td { text-align: left; vertical-align: top; padding: 0.72rem 0.85rem; font-size: 0.92rem; line-height: 1.5; } .blog-download-table th { background: rgba(255, 224, 102, 0.14); color: #3a3a3a; font-size: 0.8rem; letter-spacing: 0.05em; text-transform: uppercase; } .blog-download-table tbody tr + tr td { border-top: 1px solid rgba(0, 0, 0, 0.07); } .blog-download-table td:first-child { width: 180px; white-space: nowrap; } .blog-download-note { margin: 0.3rem 0 1rem; font-size: 0.9rem; color: #5a5a5a; } .blog-steps-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 0.8rem; } .step-card { background: rgba(255, 255, 255, 0.82); border: 1px solid rgba(0, 0, 0, 0.07); border-radius: 14px; padding: 0.95rem; } .step-card h4 { margin: 0 0 0.5rem; font-size: 1rem; color: #3f3200; } .step-card p { margin: 0; color: #5b5b5b; font-size: 0.93rem; } .enhanced-code-block { background: #191a2e; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 14px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16); padding: 1rem; margin: 0.9rem 0; } .code-header { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.6rem; } .blog-copy-btn { border: 1px solid rgba(255, 255, 255, 0.28); background: rgba(255, 255, 255, 0.08); color: rgba(255, 255, 255, 0.9); border-radius: 9px; padding: 0.3rem 0.62rem; font-size: 0.74rem; font-weight: 650; letter-spacing: 0.02em; cursor: pointer; transition: background 0.2s ease, border-color 0.2s ease, color 0.2s ease, transform 0.2s ease; } .blog-copy-btn:hover, .blog-copy-btn:focus { background: rgba(255, 255, 255, 0.14); border-color: rgba(255, 224, 102, 0.45); transform: translateY(-1px); } .blog-copy-btn.is-copied { color: #fff2b3; border-color: rgba(255, 224, 102, 0.55); background: rgba(255, 224, 102, 0.18); } .code-title { color: rgba(255, 255, 255, 0.85); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; } .code-dots { display: flex; gap: 0.32rem; } .code-dot { width: 9px; height: 9px; border-radius: 50%; } .code-dot.red { background: #ff5f56; } .code-dot.yellow { background: #ffbd2e; } .code-dot.green { background: #27ca3f; } .enhanced-code-block pre, .enhanced-code-block code { margin: 0; color: #fff; font-size: 0.9rem; line-height: 1.5; font-family: "Consolas", "Monaco", "Courier New", monospace; background: transparent; padding: 0; } .cta-section { margin-top: 1.2rem; background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 58%, var(--primary-yellow-darker) 100%); border-radius: 20px; padding: 1.8rem; box-shadow: 0 10px 24px rgba(0, 0, 0, 0.1), 0 2px 0 rgba(255, 255, 255, 0.3) inset; } .cta-content { width: 100%; max-width: 760px; margin: 0 auto; text-align: center; } .cta-title { margin: 0; font-size: 1.75rem; letter-spacing: -0.02em; color: #1f1f1f; } .cta-subtitle { margin: 0.6rem auto 0; max-width: 680px; color: #333; text-align: center; } .cta-links { margin-top: 1.2rem; display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 0.65rem; } .cta-link { display: inline-flex; justify-content: center; align-items: center; gap: 0.38rem; min-height: 54px; border-radius: 12px; border: 1px solid rgba(0, 0, 0, 0.08); background: rgba(255, 255, 255, 0.86); color: #1f1f1f; text-decoration: none; font-weight: 700; transition: transform 0.2s ease, box-shadow 0.2s ease; } .cta-link:first-child { background: #181818; color: #fff; border-color: rgba(255, 255, 255, 0.18); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.22); } .cta-link:hover, .cta-link:focus { transform: translateY(-1px); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.14); color: #1f1f1f; } .cta-link:first-child:hover, .cta-link:first-child:focus { color: #fff; box-shadow: 0 12px 24px rgba(0, 0, 0, 0.3); } .cta-note { margin-top: 1rem; border-radius: 12px; background: rgba(255, 255, 255, 0.78); border: 1px solid rgba(255, 255, 255, 0.5); padding: 0.75rem 0.9rem; color: #2f2f2f; font-weight: 600; } .cta-section-minimal .cta-actions { margin-top: 1rem; display: flex; gap: 0.65rem; justify-content: center; flex-wrap: wrap; } .cta-section-minimal { background: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.56)); border: 1px solid rgba(0, 0, 0, 0.06); box-shadow: 0 12px 28px rgba(0, 0, 0, 0.05); } .cta-section-minimal .cta-content { display: flex; flex-direction: column; align-items: center; } .cta-section-minimal .cta-subtitle { width: min(680px, 100%); margin: 0.6rem 0 0; text-align: center; } .cta-section-minimal .download-btn { margin: 0; } .cta-section-minimal .cta-action-primary, .cta-section-minimal .cta-action-secondary { min-width: 240px; min-height: 52px; border-radius: 12px; padding: 0.72rem 1.05rem; text-decoration: none; font-weight: 700; display: inline-flex; align-items: center; justify-content: center; transition: transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease, color 0.2s ease; } .cta-section-minimal .cta-action-primary { background: #181818; color: #fff; border: 1px solid rgba(255, 255, 255, 0.16); box-shadow: 0 10px 20px rgba(0, 0, 0, 0.22); } .cta-section-minimal .cta-action-primary:hover, .cta-section-minimal .cta-action-primary:focus { color: #fff; transform: translateY(-1px); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.28); } .cta-section-minimal .cta-action-secondary { background: rgba(255, 255, 255, 0.86); color: #1f1f1f; border: 1px solid rgba(0, 0, 0, 0.08); } .cta-section-minimal .cta-action-secondary:hover, .cta-section-minimal .cta-action-secondary:focus { color: #1f1f1f; transform: translateY(-1px); box-shadow: 0 8px 18px rgba(0, 0, 0, 0.14); } .article-navigation { margin-top: 1.1rem; text-align: center; } .back-to-news-btn { min-width: min(250px, 80vw); max-width: min(250px, 80vw); height: 56px; margin: 0 auto; display: inline-flex; align-items: center; justify-content: center; gap: 0.42rem; padding: 0.78rem 1.2rem; border-radius: 14px; font-size: 0.9rem; font-weight: 700; line-height: 1; letter-spacing: 0.01em; color: var(--text-primary); text-decoration: none; background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 58%, var(--primary-yellow-darker) 100%); border: none; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1), 0 2px 0 rgba(255, 255, 255, 0.3) inset; transition: transform 0.2s ease, box-shadow 0.2s ease; } .back-to-news-btn:hover, .back-to-news-btn:focus { box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), 0 2px 0 rgba(255, 255, 255, 0.4) inset; transform: translateY(-2px) scale(1.01); color: var(--text-primary); } @media (max-width: 900px) { .blog-article { padding: 1rem; } .blog-article .hero-section { padding: 2rem 1.2rem 1.7rem; } .section-card { padding: 1.2rem; } .blog-article-unified .section-card { padding: 1rem 0; } .blog-article-unified .hero-section { margin-bottom: 1.8rem; } .blog-article-arcade .model-stat-grid { grid-template-columns: 1fr; } .blog-article-arcade .arcade-demo-media { width: 100%; } } @media (max-width: 640px) { .hero-meta { justify-content: flex-start; } .meta-item { width: 100%; text-align: center; } .meta-item.meta-authors { text-align: center; padding-left: 0; padding-right: 0; } } /* ============================================================= HOMEPAGE v6 — tighter layout + stronger section hierarchy ============================================================= */ .hp-main { max-width: none; padding-top: 0; } /* --- Split Hero --- */ .hp-hero { display: grid; grid-template-columns: 1.05fr 0.95fr; gap: 2rem; align-items: center; width: 100%; max-width: 1120px; margin: 0 auto; padding: 7rem 1.5rem 9rem; min-height: auto; position: relative; } .hp-hero-left { display: flex; flex-direction: column; justify-content: center; gap: 1.15rem; max-width: 560px; } .hp-subtitle-group { display: flex; flex-direction: column; gap: 1rem; width: min(460px, 100%); } .hp-headline { font-size: clamp(2rem, 4.1vw, 3.25rem); font-weight: 400; color: #171717; line-height: 1.08; margin: 0; letter-spacing: -0.035em; } .hp-headline-top { display: inline-flex; align-items: baseline; gap: 0.22em; white-space: nowrap; } .hp-carousel-wrap { --hp-carousel-line-height: 1.14; position: relative; display: inline-block; vertical-align: baseline; overflow: hidden; text-align: left; line-height: var(--hp-carousel-line-height); padding-right: 0.06em; padding-bottom: 0.08em; } .hp-carousel-sizer { display: inline-block; visibility: hidden; color: transparent; pointer-events: none; user-select: none; font-weight: 700; line-height: var(--hp-carousel-line-height); white-space: nowrap; } .hp-carousel-track { position: absolute; top: 0; left: 0; right: 0; bottom: 0; } .hp-carousel-slot { position: absolute; top: 0; left: 0; display: block; color: var(--accent-gold-dark); font-weight: 700; white-space: nowrap; text-align: left; line-height: var(--hp-carousel-line-height); transform: translate3d(0, 100%, 0); will-change: transform; } .hp-subtitle { font-size: clamp(0.95rem, 1.8vw, 1.15rem); color: #474747; margin: 0; line-height: 1.5; font-weight: 450; } .hp-cta-row { display: flex; gap: 0.85rem; width: 100%; max-width: 460px; } .hp-cta-row .download-btn, .hp-cta-row .dev-btn { flex: 1 1 0; min-width: 0; margin: 0; padding: 0.8rem 1rem; font-size: 1rem; text-align: center; line-height: 1.1; } .hp-cta-row .download-sub, .hp-cta-row .dev-sub { font-size: 0.75rem; } .hp-hero-right { display: flex; align-items: center; justify-content: center; } .hp-demo-panel { width: 100%; max-width: 440px; background: #1a1b30; border-radius: 18px; padding: 1.4rem; height: 290px; position: relative; overflow: hidden; box-shadow: 0 18px 44px rgba(0, 0, 0, 0.18), 0 4px 10px rgba(0, 0, 0, 0.08); border: 1px solid rgba(255, 255, 255, 0.08); display: flex; flex-direction: column; justify-content: center; } .hp-demo-item { display: flex; flex-direction: column; gap: 0.5rem; height: calc(100% - 2.8rem); opacity: 0; transform: translateY(10px); transition: opacity 0.4s ease, transform 0.4s ease; position: absolute; inset: 1.4rem 1.75rem; pointer-events: none; } .hp-demo-item.active { opacity: 1; transform: translateY(0); pointer-events: auto; } .hp-demo-item.exiting-up { opacity: 0; transform: translateY(-10px); pointer-events: none; } .hp-demo-label { font-size: 0.72rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: rgba(255, 255, 255, 0.55); margin-bottom: 0.2rem; } .hp-demo-chat { display: flex; flex-direction: column; justify-content: space-between; gap: 0.5rem; height: 100%; } .hp-chat-user { background: rgba(255, 224, 102, 0.15); color: #ffe066; font-size: 0.82rem; padding: 0.66rem 0.9rem; border-radius: 15px 15px 15px 5px; line-height: 1.38; align-self: flex-start; max-width: 78%; width: fit-content; } .hp-demo-prompt { max-width: 78%; align-self: flex-start; } .hp-chat-ai { background: rgba(255, 255, 255, 0.1); color: rgba(255, 255, 255, 0.78); font-size: 0.82rem; padding: 0.6rem 0.75rem; border-radius: 10px 10px 3px 10px; line-height: 1.4; align-self: flex-end; max-width: 90%; } .hp-demo-image { display: flex; flex-direction: column; justify-content: space-between; gap: 0.7rem; height: 100%; } .hp-demo-image-placeholder { width: clamp(220px, 68vw, 300px); aspect-ratio: 2 / 1; height: auto; flex-shrink: 0; border-radius: 12px; align-self: flex-end; background: #1f2035 url("https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/generated_image.png") center/contain no-repeat; } @keyframes hp-gradient-shift { 0%, 100% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } } .hp-demo-image-prompt { font-size: 0.82rem; color: rgba(255, 255, 255, 0.55); font-style: italic; line-height: 1.4; } .hp-demo-audio { display: flex; flex-direction: column; justify-content: flex-start; gap: 0.7rem; height: 100%; } .hp-waveform { display: flex; align-items: center; justify-content: center; gap: 3px; height: 96px; width: 82%; max-width: 82%; align-self: center; border-radius: 12px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.08); padding: 0.75rem 1rem; margin-top: 20px; flex-shrink: 0; } .hp-waveform span { display: block; width: 4px; border-radius: 2px; background: var(--primary-yellow); opacity: 0.82; animation: hp-wave 1.2s ease-in-out infinite; } .hp-waveform span:nth-child(1) { height: 30%; animation-delay: 0.00s; } .hp-waveform span:nth-child(2) { height: 55%; animation-delay: 0.05s; } .hp-waveform span:nth-child(3) { height: 80%; animation-delay: 0.10s; } .hp-waveform span:nth-child(4) { height: 60%; animation-delay: 0.15s; } .hp-waveform span:nth-child(5) { height: 95%; animation-delay: 0.20s; } .hp-waveform span:nth-child(6) { height: 70%; animation-delay: 0.25s; } .hp-waveform span:nth-child(7) { height: 45%; animation-delay: 0.30s; } .hp-waveform span:nth-child(8) { height: 85%; animation-delay: 0.35s; } .hp-waveform span:nth-child(9) { height: 50%; animation-delay: 0.40s; } .hp-waveform span:nth-child(10) { height: 75%; animation-delay: 0.45s; } .hp-waveform span:nth-child(11) { height: 40%; animation-delay: 0.50s; } .hp-waveform span:nth-child(12) { height: 90%; animation-delay: 0.55s; } .hp-waveform span:nth-child(13) { height: 55%; animation-delay: 0.60s; } .hp-waveform span:nth-child(14) { height: 70%; animation-delay: 0.65s; } .hp-waveform span:nth-child(15) { height: 35%; animation-delay: 0.70s; } .hp-waveform span:nth-child(16) { height: 80%; animation-delay: 0.75s; } .hp-waveform span:nth-child(17) { height: 50%; animation-delay: 0.80s; } .hp-waveform span:nth-child(18) { height: 65%; animation-delay: 0.85s; } .hp-waveform span:nth-child(19) { height: 40%; animation-delay: 0.90s; } .hp-waveform span:nth-child(20) { height: 55%; animation-delay: 0.95s; } .hp-waveform span:nth-child(21) { height: 75%; animation-delay: 1.00s; } .hp-waveform span:nth-child(22) { height: 45%; animation-delay: 1.05s; } .hp-waveform span:nth-child(23) { height: 85%; animation-delay: 1.10s; } .hp-waveform span:nth-child(24) { height: 60%; animation-delay: 1.15s; } .hp-waveform span:nth-child(25) { height: 50%; animation-delay: 1.20s; } @keyframes hp-wave { 0%, 100% { transform: scaleY(1); } 50% { transform: scaleY(0.35); } } .hp-audio-text { align-self: flex-end; } /* --- Shared section system --- */ .hp-reveal { opacity: 0; transform: translateY(34px); transition: opacity 0.65s cubic-bezier(0.16, 1, 0.3, 1), transform 0.65s cubic-bezier(0.16, 1, 0.3, 1); } .hp-reveal.hp-visible { opacity: 1; transform: translateY(0); } .hp-section { width: 100%; max-width: 1120px; margin: 0 auto; padding: 3.4rem 1.8rem; text-align: center; position: relative; } .hp-section + .hp-section::before { content: ''; display: block; width: 72px; height: 1px; background: rgba(0, 0, 0, 0.1); margin: 0 auto; position: absolute; top: 0; left: 50%; transform: translateX(-50%); } .hp-section-label { display: inline-block; font-size: 0.72rem; font-weight: 700; letter-spacing: 0.14em; text-transform: uppercase; color: var(--accent-gold-dark); background: rgba(255, 224, 102, 0.2); padding: 0.36rem 0.95rem; border-radius: 100px; margin-bottom: 0.95rem; } .hp-section-heading { font-size: clamp(1.65rem, 3.2vw, 2.5rem); font-weight: 750; color: var(--text-primary); margin: 0 0 0.9rem; letter-spacing: -0.03em; line-height: 1.15; } .hp-section-desc { font-size: clamp(0.96rem, 1.65vw, 1.08rem); color: #505050; margin: 0 auto 2rem; max-width: 760px; line-height: 1.62; } /* --- Open source section --- */ .hp-open-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2fr); gap: 1.2rem; align-items: start; text-align: left; } @media (min-width: 1000px) { .hp-open-layout { align-items: stretch; } .hp-open-story, .hp-open-stack { height: 100%; } } .hp-open-story, .hp-open-stack { background: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.56)); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 20px; padding: 1.4rem; box-shadow: 0 12px 28px rgba(0, 0, 0, 0.05); } .hp-open-story { display: flex; flex-direction: column; } .hp-open-story h3, .hp-open-stack h3 { margin: 0; font-size: 1.15rem; color: #1e1e1e; letter-spacing: -0.01em; } .hp-community-cta { display: grid; grid-template-columns: 1fr; gap: 0.62rem; margin-top: 1.5rem; margin-left: 10px; justify-items: start; } .hp-community-btn { display: flex; align-items: stretch; text-decoration: none; border-radius: 14px; padding: 0; border: 1px solid transparent; transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; overflow: hidden; min-height: 72px; width: 100%; max-width: 270px; } .hp-community-btn:hover { transform: translateY(-2px); } .hp-community-btn-icon-col { width: 3rem; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; background: rgba(255, 255, 255, 0.06); } .hp-community-btn-text { display: flex; flex-direction: column; justify-content: center; padding: 0.62rem 0.9rem 0.58rem; } .hp-community-btn-icon { display: inline-flex; width: 1.55rem; height: 1.55rem; opacity: 0.95; } .hp-community-btn-icon img, .hp-community-btn-icon svg { width: 100%; height: 100%; display: block; object-fit: contain; fill: currentColor; } .hp-community-btn-label { font-size: 1.08rem; font-weight: 700; line-height: 1.16; } .hp-community-btn-sub { margin-top: 0.2rem; font-size: 0.92rem; line-height: 1.2; font-weight: 600; letter-spacing: 0.01em; } .hp-community-btn-github { background: #181818; color: #fff; border-color: rgba(255, 255, 255, 0.08); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.17); } .hp-community-btn-github .hp-community-btn-sub { color: rgba(255, 255, 255, 0.72); } .hp-community-btn-github .hp-community-btn-icon-col { background: rgba(255, 255, 255, 0.07); } .hp-community-btn-discord { background: linear-gradient(145deg, #6677f9, #5865f2); color: #fff; box-shadow: 0 8px 16px rgba(88, 101, 242, 0.24); } .hp-community-btn-discord .hp-community-btn-sub { color: rgba(255, 255, 255, 0.8); } .hp-community-btn-discord .hp-community-btn-icon-col { background: rgba(255, 255, 255, 0.12); } .hp-community-btn-github:hover { border-color: rgba(255, 224, 102, 0.36); box-shadow: 0 12px 20px rgba(0, 0, 0, 0.2); } .hp-community-btn-discord:hover { box-shadow: 0 12px 22px rgba(88, 101, 242, 0.3); } .hp-logo-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); justify-items: center; align-items: center; gap: 0.45rem 0.72rem; margin-top: 0.95rem; /* Partner logo tuning controls (desktop defaults). */ --hp-logo-lockup-width: 176px; --hp-logo-base-height: 34px; --hp-logo-scale-1: 1.3; /* llama.cpp */ --hp-logo-scale-2: 2; /* ONNX Runtime */ --hp-logo-scale-3: 1.08; /* FastFlowLM */ --hp-logo-scale-4: 1.5; /* Ryzen AI */ --hp-logo-scale-5: 1.3; /* ROCm */ --hp-logo-scale-6: 2; /* Hugging Face */ --hp-logo-scale-7: 1.1; /* Vulkan */ --hp-logo-scale-8: 1.25; /* whisper.cpp */ --hp-logo-scale-9: 2.5; /* stable-diffusion.cpp */ --hp-logo-scale-10: 2.3; /* Kokoros */ } .hp-logo-chip { min-height: 70px; display: flex; justify-content: center; align-items: center; padding: 0.26rem 0.32rem; text-decoration: none; filter: grayscale(0.04) saturate(0.97); transition: transform 0.2s ease, filter 0.2s ease, opacity 0.2s ease, background-color 0.2s ease; opacity: 0.94; position: relative; border-radius: 10px; --hp-scale: 1; width: 100%; } .hp-logo-chip::before { content: ""; position: absolute; inset: 6px; border-radius: 10px; background: linear-gradient(180deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.06)); opacity: 0; transition: opacity 0.2s ease; } .hp-logo-chip:hover { transform: translateY(-1px); filter: grayscale(0) saturate(1); opacity: 1; } .hp-logo-chip:hover::before { opacity: 1; } .hp-logo-lockup { width: min(var(--hp-logo-lockup-width), 100%); height: auto; display: inline-flex; justify-content: center; align-items: center; overflow: visible; position: relative; z-index: 1; } .hp-logo-chip img { width: auto; max-width: min(182px, 100%); height: max(28px, calc(var(--hp-logo-base-height) * var(--hp-scale))); object-fit: contain; mix-blend-mode: multiply; } .hp-logo-chip:nth-child(1) { --hp-scale: var(--hp-logo-scale-1); } .hp-logo-chip:nth-child(2) { --hp-scale: var(--hp-logo-scale-2); } .hp-logo-chip:nth-child(3) { --hp-scale: var(--hp-logo-scale-3); } .hp-logo-chip:nth-child(4) { --hp-scale: var(--hp-logo-scale-4); } .hp-logo-chip:nth-child(5) { --hp-scale: var(--hp-logo-scale-5); } .hp-logo-chip:nth-child(6) { --hp-scale: var(--hp-logo-scale-6); } .hp-logo-chip:nth-child(7) { --hp-scale: var(--hp-logo-scale-7); } .hp-logo-chip:nth-child(8) { --hp-scale: var(--hp-logo-scale-8); } .hp-logo-chip:nth-child(9) { --hp-scale: var(--hp-logo-scale-9); } .hp-logo-chip:nth-child(10) { --hp-scale: var(--hp-logo-scale-10); } .hp-section-release { margin-top: 1.3rem; } .hp-release-carousel { display: grid; grid-template-columns: auto 700px auto; align-items: center; justify-content: center; gap: 0.25rem; } .hp-release-nav { width: auto; height: auto; border-radius: 0; border: none; background: transparent; color: rgba(33, 33, 33, 0.52); box-shadow: none; display: inline-flex; align-items: center; justify-content: center; padding: 0 0.28rem; cursor: pointer; transition: color 0.2s ease, opacity 0.2s ease; } .hp-release-nav span { font-size: 3.15rem; line-height: 0.95; } .hp-release-nav:hover, .hp-release-nav:focus { color: rgba(33, 33, 33, 0.82); } .hp-release-nav:disabled { opacity: 0.24; cursor: not-allowed; } .hp-release-section .release-announcement { margin: 0 auto; width: 700px; min-width: 700px; max-width: 700px; min-height: 300px; animation: none; background: linear-gradient(180deg, rgba(255, 255, 255, 0.78), rgba(255, 255, 255, 0.56)); border: 1px solid rgba(0, 0, 0, 0.06); border-radius: 20px; box-shadow: 0 12px 28px rgba(0, 0, 0, 0.05); backdrop-filter: none; padding: 0; } .hp-release-section .release-announcement::before { display: none; } .hp-release-section .release-content { margin-bottom: 0; padding: 1.2rem 1.2rem 0.72rem; } .hp-release-section .release-info { text-align: left; } .hp-release-section .release-name { margin: 0 0 0.3rem 0; font-size: 1.28rem; color: #2a2a2a; } .hp-release-section .release-date { margin: 0 0 0.86rem; } .hp-release-section .release-link { margin: 0 auto 1.05rem; min-width: min(250px, 80vw); max-width: min(250px, 80vw); height: 70px; display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem; padding: 0.78rem 1.2rem; border-radius: 14px; font-size: 0.94rem; font-weight: 700; line-height: 1; letter-spacing: 0.01em; color: var(--text-primary); text-decoration: none; background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 58%, var(--primary-yellow-darker) 100%); border: none; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1), 0 2px 0 rgba(255, 255, 255, 0.3) inset; transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease; } .hp-release-section .release-link:hover, .hp-release-section .release-link:focus { box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), 0 2px 0 rgba(255, 255, 255, 0.4) inset; transform: translateY(-2px) scale(1.02); } .hp-release-section .release-link:active { background: var(--primary-yellow); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transform: scale(0.98); } /* --- Technical details section --- */ .hp-tech-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.9rem; text-align: left; } .hp-tech-card { background: rgba(255, 255, 255, 0.64); border: 1px solid rgba(0, 0, 0, 0.07); border-radius: 15px; padding: 1rem; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.04); } .hp-tech-icon { width: 42px; height: 42px; object-fit: contain; margin-bottom: 0.62rem; } .hp-tech-card h3 { margin: 0 0 0.4rem; font-size: 0.95rem; color: #242424; line-height: 1.3; } .hp-tech-card p { margin: 0; font-size: 0.83rem; line-height: 1.45; color: #535353; } /* --- Unified API section --- */ .hp-api-layout { display: flex; flex-direction: column; align-items: center; gap: 1.1rem; text-align: left; margin-top: 1.25rem; --hp-api-block-width: 700px; } .hp-api-modalities { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 0.7rem; width: min(var(--hp-api-block-width), 100%); } .hp-api-modality { appearance: none; -webkit-appearance: none; background: linear-gradient(145deg, #191a2e, #1f2036); color: rgba(255, 255, 255, 0.95); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 14px; padding: 0.62rem 0.72rem; text-align: left; cursor: pointer; font: inherit; -webkit-tap-highlight-color: transparent; outline: none; position: relative; overflow: hidden; transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; min-height: 52px; } .hp-api-modality:hover { transform: translateY(-1px); border-color: rgba(255, 224, 102, 0.32); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.16); } .hp-api-modality.is-active { border-color: rgba(255, 224, 102, 0.55); background: linear-gradient(145deg, #21233f, #2a2d50); box-shadow: 0 12px 24px rgba(0, 0, 0, 0.2), inset 0 0 0 1px rgba(255, 224, 102, 0.24); } .hp-api-modality:focus-visible { border-color: rgba(255, 224, 102, 0.42); } .hp-api-modality:active { background: linear-gradient(145deg, #191a2e, #1f2036); transform: translateY(0); } .hp-api-modality.is-active:active { background: linear-gradient(145deg, #1b1d33, #242642); } .hp-api-modality h3 { margin: 0; font-size: 0.92rem; font-weight: 620; color: rgba(255, 255, 255, 0.96); } .hp-api-modality.is-active h3 { color: #fff2b3; } .hp-api-sample-panel { background: #191a2e; color: rgba(255, 255, 255, 0.9); border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 16px; padding: 1.1rem 1.15rem; height: auto; width: min(var(--hp-api-block-width), 100%); box-sizing: border-box; display: flex; flex-direction: column; align-self: center; } .hp-api-sample-head { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; } .hp-api-copy-btn { border: 1px solid rgba(255, 255, 255, 0.18); background: rgba(255, 255, 255, 0.06); color: rgba(255, 255, 255, 0.9); border-radius: 9px; line-height: 0; padding: 0.46rem; cursor: pointer; transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease; } .hp-api-copy-btn svg { width: 0.95rem; height: 0.95rem; display: block; fill: currentColor; } .hp-api-copy-btn:hover, .hp-api-copy-btn:focus { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 224, 102, 0.42); transform: translateY(-1px); } .hp-api-copy-btn.is-copied { color: #ffe680; border-color: rgba(255, 224, 102, 0.55); background: rgba(255, 224, 102, 0.16); } .hp-api-sample-method { margin: 0; color: rgba(255, 255, 255, 0.72); font-size: 0.92rem; font-weight: 600; gap: 0.48rem; } .hp-api-cta { margin-top: 1.6rem; display: flex; justify-content: center; } .hp-api-cta .marketplace-cta-btn { margin: 0; min-width: min(250px, 80vw); max-width: min(250px, 80vw); height: 70px; display: inline-flex; align-items: center; justify-content: center; line-height: 1.2; } .hp-api-sample-panel pre { margin: 0.68rem 0 0; border-radius: 10px; background: rgba(255, 255, 255, 0.08); padding: 0.86rem 0.95rem; overflow: visible; max-height: none; white-space: pre-wrap !important; overflow-wrap: normal !important; word-break: normal !important; word-wrap: normal !important; } .hp-api-sample-panel code { color: #ffffff; font-size: 0.9rem; line-height: 1.5; font-family: "Consolas", "Monaco", "Courier New", monospace; display: block; min-width: 0; white-space: pre-wrap !important; overflow-wrap: normal !important; word-break: normal !important; word-wrap: normal !important; } .hp-api-method { font-family: "Consolas", "Monaco", "Courier New", monospace; font-size: 0.72rem; color: rgba(255, 255, 255, 0.52); margin-bottom: 0.6rem; display: flex; align-items: center; gap: 0.4rem; } .hp-method-badge { background: rgba(255, 224, 102, 0.14); color: #ffe066; font-size: 0.62rem; font-weight: 700; padding: 0.15rem 0.38rem; border-radius: 4px; letter-spacing: 0.04em; } /* --- Ecosystem section --- */ .hp-eco-apps { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1.2rem 0.85rem; text-align: center; margin-top: 1.35rem; width: min(860px, 100%); margin-left: auto; margin-right: auto; } .hp-eco-app { display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 0.45rem; padding: 0.45rem 0.35rem; border-radius: 12px; text-decoration: none; background: transparent; border: 1px solid transparent; transition: all 0.22s ease; } .hp-eco-app:hover { background: rgba(255, 255, 255, 0.58); transform: translateY(-2px); box-shadow: 0 8px 14px rgba(0, 0, 0, 0.05); border-color: rgba(0, 0, 0, 0.06); } .hp-eco-app img { width: 70px; height: 70px; object-fit: contain; border-radius: 14px; flex-shrink: 0; } .hp-eco-app-name { font-size: 0.82rem; font-weight: 620; color: #1f1f1f; line-height: 1.25; text-align: center; max-width: 100%; } .hp-eco-cta { margin-top: 2.5rem; display: flex; justify-content: center; } .hp-eco-cta .marketplace-cta-btn { margin: 0; min-width: min(250px, 80vw); max-width: min(250px, 80vw); height: 70px; display: inline-flex; align-items: center; justify-content: center; gap: 0.45rem; padding: 0.78rem 1.2rem; border-radius: 14px; font-size: 0.94rem; font-weight: 700; line-height: 1; letter-spacing: 0.01em; color: var(--text-primary); text-decoration: none; background: linear-gradient(135deg, var(--primary-yellow) 0%, var(--primary-yellow-dark) 58%, var(--primary-yellow-darker) 100%); border: none; box-shadow: 0 6px 20px rgba(0, 0, 0, 0.1), 0 2px 0 rgba(255, 255, 255, 0.3) inset; transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease; } .hp-eco-cta .marketplace-cta-btn:hover, .hp-eco-cta .marketplace-cta-btn:focus { box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15), 0 2px 0 rgba(255, 255, 255, 0.4) inset; transform: translateY(-2px) scale(1.02); } .hp-eco-cta .marketplace-cta-btn:active { background: var(--primary-yellow); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transform: scale(0.98); } /* --- Homepage responsive --- */ @media (max-width: 1024px) { .hp-main { margin-top: 0 !important; } } @media (max-width: 980px) { .hp-hero { grid-template-columns: 1fr; justify-items: center; gap: 1.75rem; padding: 2.8rem 1rem 4rem; text-align: center; } .hp-hero-left { text-align: center; align-items: center; width: 100%; max-width: 560px; margin: 0 auto; } .hp-hero-right { width: 100%; margin: 0 auto; } .hp-subtitle-group { width: 100%; max-width: 440px; } .hp-demo-panel { max-width: 440px; height: 280px; margin: 0 auto; } .hp-open-layout { grid-template-columns: 1fr; } .hp-open-story { width: 100%; max-width: 440px; justify-self: center; } .hp-open-story h3 { text-align: center; } .hp-community-cta { margin-left: 0; justify-items: center; } .hp-tech-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } .hp-api-modalities { grid-template-columns: repeat(3, minmax(0, 1fr)); } .hp-api-sample-panel { height: auto; } .hp-release-carousel { grid-template-columns: minmax(0, 1fr); justify-items: stretch; gap: 0; position: relative; padding: 0 2.1rem; } .hp-release-nav { position: absolute; top: 50%; transform: translateY(-50%); z-index: 3; padding: 0 0.14rem; } .hp-release-nav span { font-size: 2.1rem; line-height: 1; } .hp-release-nav-prev { left: 0.1rem; } .hp-release-nav-next { right: 0.1rem; } .hp-release-section .release-announcement { width: min(700px, 100%); min-width: 0; max-width: min(700px, 100%); min-height: 300px; } .hp-logo-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0.34rem 0.58rem; --hp-logo-base-height: 32px; --hp-logo-lockup-width: 164px; } .hp-logo-chip { min-height: 64px; } .hp-eco-apps { grid-template-columns: repeat(4, minmax(0, 1fr)); } } @media (max-width: 720px) { .hp-section { padding: 2.8rem 1rem; } .hp-headline { font-size: 1.9rem; } .hp-cta-row { flex-direction: column; width: auto; max-width: 460px; align-items: center; } .hp-cta-row .download-btn, .hp-cta-row .dev-btn { width: min(250px, 80vw); min-width: min(250px, 80vw); max-width: min(250px, 80vw); height: 70px; min-height: 70px; padding: 0.8rem 1rem; justify-content: center; } .hp-demo-panel { height: 265px; } .hp-demo-image-placeholder { height: 128px; } .hp-api-modalities { grid-template-columns: repeat(2, minmax(0, 1fr)); } .hp-api-sample-panel code { font-size: 0.84rem; } .hp-logo-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } .hp-eco-apps { grid-template-columns: repeat(3, minmax(0, 1fr)); } } @media (max-width: 480px) { .hp-tech-grid { grid-template-columns: 1fr; } .hp-api-modalities { grid-template-columns: 1fr; } .hp-api-sample-panel { height: auto; padding: 0.95rem; } .hp-demo-panel, .hp-open-story, .hp-open-stack, .hp-tech-card, .hp-api-modality, .hp-api-sample-panel, .hp-eco-app { border-radius: 12px; } .hp-logo-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.24rem 0.4rem; --hp-logo-base-height: 30px; --hp-logo-lockup-width: 144px; } .hp-logo-chip { min-height: 58px; } .hp-eco-apps { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.62rem 0.5rem; } } lemonade-sdk-lemonade-d88f5d9/docs/code.md000066400000000000000000000021161515430344400204630ustar00rootroot00000000000000# Lemonade Code Structure The Lemonade source code has a few major top-level directories: - `.github`: defines CI workflows for GitHub Actions. - `docs`: documentation for the entire project. - `examples`: example scripts and demos. - `src`: source code. - `/app`: Electron desktop application (Model Manager, Chat UI). - `/cpp`: C++ implementation of the Lemonade Server. - `/lemonade`: Python package for the `lemonade-eval` CLI. - `/tools`: implements `Tool` and defines the tools built in to the `lemonade-eval` CLI (e.g., `load`, `bench`, `oga-load`, `lm-eval-harness`, etc.). - `/sequence.py`: implements `Sequence` and defines the plugin API for `Tool`s. - `/cli.py`: implements the `lemonade-eval` CLI entry point. - `/common`: functions common to the other modules. - `/version.py`: defines the package version number. - `/state.py`: implements the `State` class. - `setup.py`: defines the `lemonade-sdk` wheel. - `test`: tests for Lemonade. lemonade-sdk-lemonade-d88f5d9/docs/contribute.md000066400000000000000000000114151515430344400217310ustar00rootroot00000000000000# Lemonade SDK Contribution Guide The Lemonade SDK project welcomes contributions from everyone! See [code organization](https://github.com/lemonade-sdk/lemonade/blob/main/docs/code.md) for an overview of the repository. ## Collaborate with Your App Lemonade Server integrates quickly with most OpenAI-compatible LLM apps. You can: - Share an example of your app using Lemonade via [Discord](https://discord.gg/5xXzkMu8Zk), [GitHub Issue](https://github.com/lemonade-sdk/lemonade/issues), or [email](mailto:lemonade@amd.com). - Contribute a guide by adding a `.md` file to the [server apps folder](https://github.com/lemonade-sdk/lemonade/tree/main/docs/server/apps). Follow the style of the [Open WebUI guide](./server/apps/open-webui.md). Guides should: - Work in under 10 minutes. - Require no code changes to the app. - Use OpenAI API-compatible apps with configurable base URLs. ## Backend Contributions To contribute code or examples, first open an [Issue](https://github.com/lemonade-sdk/lemonade/issues) with: - A descriptive title. - Relevant labels (`enhancement`, `good first issue`, etc.). - A proposal explaining what you're contributing. - The use case it supports. One of the maintainers will get back to you ASAP with guidance. ## UI/Frontend Contributions **Current UI Development Approach:** For now, UI and frontend development is being handled exclusively by core maintainers. Here's why: AI-assisted coding has made building UIs incredibly fast, but it's also made reviewing UI PRs quite challenging. UI changes often involve complex state management, visual consistency, accessibility considerations, and cross-platform considerations that require deep context about the entire application architecture. **How You Can Still Influence the UI:** We want your creativity and insights! Share UI/UX ideas, report bugs, or request features via [Issue](https://github.com/lemonade-sdk/lemonade/issues) or [Discord](https://discord.gg/5xXzkMu8Zk). Include mockups, screenshots, and reproduction steps where relevant. **UI Scope: Management, Not Competition:** Our UI exists to facilitate Lemonade management - not to compete with the apps built on top of Lemonade. While it's tempting to add agentic workflows, advanced chat features, or other sophisticated capabilities, that's not our goal. We focus on making model management, configuration, and monitoring delightful and effortless. Defining this line isn't always easy, but use this principle as your guide when considering new UI features. ## Issues Use [Issues](https://github.com/lemonade-sdk/lemonade/issues) to report bugs or suggest features. A maintainer will apply one of these labels to indicate the status: - `on roadmap`: planned for development. - `good first issue`: open for contributors. - `needs detail`: needs clarification before proceeding. - `wontfix`: out of scope or unmaintainable. ## Pull Requests Submit a PR to contribute code. Maintainers: - @danielholanda - @jeremyfowers - @ramkrishna2910 - @vgodsoe Discuss major changes via an Issue first. ## Code Formatting We require that all Python files in this repo adhere to black formatting. This is enforced with a black check in CI workflows. ### Running Black formatting The easiest way to ensure proper formatting is to enable the black formatter in VSCode with format-on-save: 1. **Install the Python extension**: Install the Python extension for VSCode if you haven't already. 2. **Set black as the default formatter**: - Open VSCode settings (Ctrl/Cmd + ,) - Search for "Formatter" - Set the Python default formatter to "black" 3. **Enable format-on-save**: - In VSCode settings, search for "format on save" - Check the "Format On Save" option This will automatically format your code according to black standards whenever you save a file. #### Alternative Setup You can also install black directly and run it manually: ```bash # Install black (if not already installed) pip install black # Run black formatter on your file black your_file.py ``` ### Linting We use linting tools to maintain code quality and catch potential issues. The project uses standard Python linting tools that are automatically run in CI. #### Running Linters Locally To run linting checks locally before submitting a PR: ```bash # Install linting dependencies (if not already installed) pip install pylint # Run pylint from the root of the repo pylint src/lemonade --rcfile .pylintrc --disable E0401 ``` This will show linting warnings and errors in your terminal. ## Testing Tests are run automatically on each PR. These include: - Linting - Code formatting (`black`) - Unit tests - End-to-end tests To run tests locally, use the commands in `.github/workflows/`. ## Versioning We follow [Semantic Versioning](https://github.com/lemonade-sdk/lemonade/blob/main/docs/versioning.md). lemonade-sdk-lemonade-d88f5d9/docs/dev-getting-started.md000066400000000000000000001011221515430344400234270ustar00rootroot00000000000000# Lemonade Development This guide covers everything you need to build, test, and contribute to Lemonade from source. Whether you're fixing a bug, adding a feature, or just exploring the codebase, this document will help you get started. ## Table of Contents - [Components](#components) - [Building from Source](#building-from-source) - [Prerequisites](#prerequisites) - [Build Steps](#build-steps) - [Build Outputs](#build-outputs) - [Building the Electron Desktop App (Optional)](#building-the-electron-desktop-app-optional) - [Platform-Specific Notes](#platform-specific-notes) - [Building Installers](#building-installers) - [Windows Installer (WiX/MSI)](#windows-installer-wixmsi) - [Linux .deb Package (Debian/Ubuntu)](#linux-deb-package-debianubuntu) - [Linux .rpm Package (Fedora, RHEL etc)](#linux-rpm-package-fedora-rhel-etc) - [Developer IDE & IDE Build Steps](#developer-ide--ide-build-steps) - [Code Structure](#code-structure) - [Architecture Overview](#architecture-overview) - [Overview](#overview) - [Client-Server Communication](#client-server-communication) - [Dependencies](#dependencies) - [Usage](#usage) - [lemonade-router (Server Only)](#lemonade-router-server-only) - [lemonade-server.exe (Console CLI Client)](#lemonade-serverexe-console-cli-client) - [lemonade-tray.exe (GUI Tray Launcher)](#lemonade-trayexe-gui-tray-launcher---windows-only) - [Logging and Console Output](#logging-and-console-output) - [Testing](#testing) - [Basic Functionality Tests](#basic-functionality-tests) - [Integration Tests](#integration-tests) - [Development](#development) - [Code Style](#code-style) - [Key Resources](#key-resources) - [License](#license) ## Components Lemonade consists of these main executables: - **lemonade-router.exe** - Core HTTP server executable that handles requests and LLM backend orchestration - **lemonade-server.exe** - Console CLI client for terminal users that manages server lifecycle, executes commands via HTTP API - **lemonade-tray.exe** (Windows only) - GUI tray launcher for desktop users, automatically starts `lemonade-server.exe serve` - **lemonade-log-viewer.exe** (Windows only) - Log file viewer with live tail support and installer-friendly file sharing ## Building from Source ### Prerequisites **All Platforms:** - CMake 3.28 or higher - C++17 compatible compiler - Git (for fetching dependencies) - Internet connection (first build downloads dependencies) **Windows:** - Visual Studio 2022 or later (2022 and 2026 are supported via CMake presets) - WiX 5.x (only required for building the installer) **Linux:** - Ninja build system (optional, recommended) ### Build Steps A helper script is available that will set up the build environment on popular Linux distributions and macOS. This will prompt to install dependencies via native package managers and create the build directory. **Linux / macOS** ```bash ./setup.sh ``` **Windows** ```shell ./setup.ps1 ``` Build by running: **Linux / macOS** ```bash cmake --build --preset default ``` **Windows (Visual Studio 2022)** ```powershell cmake --build --preset windows ``` **Windows (Visual Studio 2026)** ```powershell cmake --build --preset vs18 ``` ### Build Outputs - **Windows:** - `build/Release/lemonade-router.exe` - HTTP server - `build/Release/lemonade-server.exe` - Console CLI client - `build/Release/lemonade-tray.exe` - GUI tray launcher - `build/Release/lemonade-log-viewer.exe` - Log file viewer - **Linux/macOS:** - `build/lemonade-router` - HTTP server - `build/lemonade-server` - Console CLI client - **Resources:** Automatically copied to `build/Release/resources/` on Windows, `build/resources/` on Linux/macOS (web UI files, model registry, backend version configuration) ### Building the Electron Desktop App (Optional) The tray menu's "Open app" option and the `lemonade-server run` command can launch the Electron desktop app. To include it in your build: Build the Electron app using CMake (requires Node.js 20+): **Linux** ```bash cmake --build --preset default --target electron-app ``` **Windows (Visual Studio 2022)** ```powershell cmake --build --preset windows --target electron-app ``` **Windows (Visual Studio 2026)** ```powershell cmake --build --preset vs18 --target electron-app ``` This will: 1. Copy src/app to build/app-src (keeps source tree clean) 2. Run npm install in build/app-src 3. Build to build/app/linux-unpacked/ (Linux) or build/app/win-unpacked/ (Windows) The tray app searches for the Electron app in these locations: - **Windows installed**: `../app/Lemonade.exe` (relative to bin/ directory) - **Windows development**: `../app/win-unpacked/Lemonade.exe` (from build/Release/) - **Linux installed**: `/usr/local/share/lemonade-server/app/lemonade` - **Linux development**: `../app/linux-unpacked/lemonade` (from build/) If not found, the "Open app" menu option is hidden but everything else works. ### Building an AppImage (Linux Only) To create a standalone AppImage package that can run on any Linux distribution: ```bash cmake --build --preset default --target appimage ``` This will: 1. Copy the Electron app source to a separate build directory 2. Set the package.json version to match the CMake project version 3. Install npm dependencies 4. Build the renderer with production optimizations 5. Package the application as an AppImage using electron-builder The generated AppImage will be located in: - `build/app-appimage/Lemonade--.AppImage` The AppImage is a self-contained executable that includes all dependencies and can be run on any Linux distribution without installation. Simply make it executable and run it: ```bash chmod +x build/app-appimage/Lemonade-*.AppImage ./build/app-appimage/Lemonade-*.AppImage ``` ### Platform-Specific Notes **Windows:** - The build uses static linking to minimize DLL dependencies - All dependencies are built from source (no external DLL requirements) - Security features enabled: Control Flow Guard, ASLR, DEP **Linux:** - Linux builds are headless-only (no tray application) by default - This avoids LGPL dependencies (GTK3, libappindicator3, libnotify) - Run server using: `lemonade-server serve` (headless mode is automatic) - Fully functional for server operations and model management - Uses permissively licensed dependencies only (MIT, Apache 2.0, BSD, curl license) - Clean .deb package with only runtime files (no development headers) - PID file system for reliable process management - Proper graceful shutdown - all child processes cleaned up correctly - File locations: - Installed binaries: `/opt/bin` - Downloaded backends (llama-server, ryzenai-server): `~/.cache/lemonade/bin/` - Model downloads: `~/.cache/huggingface/` (follows HF conventions) - Runtime files (PID, lock, log): `$XDG_RUNTIME_DIR/lemonade/` when set and writable, otherwise `/tmp/` **macOS (beta):** - Uses native system frameworks (Cocoa, Foundation) - ARM Macs use Metal backend by default for llama.cpp - macOS support is currently in beta; a signed and notarized `.pkg` installer is available from the [releases page](https://github.com/lemonade-sdk/lemonade/releases/latest) ## Building Installers ### Windows Installer (WiX/MSI) **Prerequisites:** - WiX Toolset 5.0.2 installed from [wix-cli-x64.msi](https://github.com/wixtoolset/wix/releases/download/v5.0.2/wix-cli-x64.msi) - Completed C++ build (see above) **Building:** Using PowerShell script (recommended): ```powershell cd src\cpp .\build_installer.ps1 ``` Manual build using CMake: ```powershell cd src\cpp\build cmake --build . --config Release --target wix_installer ``` **Installer Output:** Creates `lemonade-server-minimal.msi` which: - MSI-based installer (Windows Installer technology) - **Per-user install (default):** Installs to `%LOCALAPPDATA%\lemonade_server\`, adds to user PATH, no UAC required - **All-users install (CLI only):** Installs to `%PROGRAMFILES%\Lemonade Server\`, adds to system PATH, requires elevation - Creates Start Menu shortcuts (launches `lemonade-tray.exe`) - Optionally creates desktop shortcut and startup entry - Uses Windows Installer Restart Manager to gracefully close running processes - Includes all executables (router, server, tray, log-viewer) - Proper upgrade handling between versions - Includes uninstaller **Available Installers:** - `lemonade-server-minimal.msi` - Server only (~3 MB) - `lemonade.msi` - Full installer with Electron desktop app (~105 MB) **Installation:** For detailed installation instructions including silent install, custom directories, and all-users installation, see the [Server Integration Guide](../../docs/server/server_integration.md#windows-installation). ### Linux .deb Package (Debian/Ubuntu) **Prerequisites:** - Completed C++ build (see above) **Building:** ```bash cd build cpack ``` **Package Output:** Creates `lemonade-server__amd64.deb` (e.g., `lemonade-server_9.0.3_amd64.deb`) which: - Installs to `/opt/bin/` (executables) - Installs resources to `/opt/share/lemonade-server/` - Creates desktop entry in `/opt/share/applications/` - Declares dependencies: libcurl4, libssl3, libz1 - Package size: ~2.2 MB (clean, runtime-only package) - Includes postinst script that creates writable `/opt/share/lemonade-server/llama/` directory **Installation:** ```bash # Replace with the actual version (e.g., 9.0.0) sudo apt install ./lemonade-server__amd64.deb ``` **Uninstallation:** ```bash sudo dpkg -r lemonade-server ``` **Post-Installation:** The executables will be available in PATH: ```bash lemonade-server --help lemonade-router --help # Start server in headless mode: lemonade-server serve --no-tray # Or just: lemonade-server serve ``` ### Linux .rpm Package (Fedora, RHEL etc) Very similar to the Debian instructions above with minor changes **Building:** ```bash cd build cpack -G RPM ``` **Package Output:** Creates `lemonade-server-.x86_64.rpm` (e.g., `lemonade-server-9.1.2.x86_64.rpm`) and resources are installed as per DEB version above **Installation:** ```bash # Replace with the actual version (e.g., 9.0.0) sudo dnf install ./lemonade-server-.x86_64.rpm ``` **Uninstallation:** ```bash sudo dnf remove lemonade-server ``` **Post-Installation:** Same as .deb above **macOS:** ### Building from Source on MacOS for M-Series / arm64 Family #### Macos Notary Tool Command For access with P ``` xcrun notarytool store-credentials AC_PASSWORD --apple-id "your-apple-id@example.com" --team-id "your-team-id" --private-key "/path/to/AuthKey_XXXXXX.p8" ``` or For access with API password ``` xcrun notarytool store-credentials AC_PASSWORD --apple-id "your-apple-id@example.com" --team-id "your-team-id" --password "" ``` Get your team id at: https://developer.apple.com/account #### Cmake build instructions ```bash # Install Xcode command line tools xcode-select --install # Navigate to the C++ source directory cd src/cpp # Create and enter build directory mkdir build cd build # Configure with CMake cmake .. # Build with all cores cmake --build . --config Release -j ``` ### CMake Targets The build system provides several CMake targets for different build configurations: - **`lemonade-router`**: The main HTTP server executable that handles LLM inference requests - **`package-macos`**: Creates a signed macOS installer package (.pkg) using productbuild - **`notarize_package`**: Builds and submits the package to Apple for notarization and staples the ticket - **`electron-app`**: Builds the Electron-based GUI application - **`prepare_electron_app`**: Prepares the Electron app for inclusion in the installer ### Building and Notarizing for Distribution To build a notarized macOS installer for distribution: 1. **Prerequisites**: - Apple Developer Program membership - Valid Developer ID Application and Installer certificates - App-specific password for notarization - Xcode command line tools 2. **Set Environment Variables**: ```bash export DEVELOPER_ID_APPLICATION_IDENTITY="Developer ID Application: Your Name (TEAMID)" export DEVELOPER_ID_INSTALLER_IDENTITY="Developer ID Installer: Your Name (TEAMID)" export AC_PASSWORD="your-app-specific-password" ``` 3. **Configure Notarization Keychain Profile**: ```bash xcrun notarytool store-credentials "AC_PASSWORD" \ --apple-id "your-apple-id@example.com" \ --team-id "YOURTEAMID" \ --password "your-app-specific-password" ``` 4. **Build and Notarize**: ```bash cd src/cpp/build cmake --build . --config Release --target package-macos cmake --build . --config Release --target notarize_package ``` The notarization process will: - Submit the package to Apple's notarization service - Wait for approval - Staple the notarization ticket to the package **Note**: The package is signed with hardened runtime entitlements during the build process for security. ### Developer IDE & IDE Build Steps #### Visual Studio Code Setup Guide 1. Clone the repository into a blank folder locally on your computer. 2. Open the folder in visual studio code. 3. Install Dev Containers extension in Visual Studio Code by using control + p to open the command bar at the top of the IDE or if on mac with Cmd + p. 4. Type "> Extensions: Install Extensions" which will open the Extensions side panel. 5. in the extensions search type ```Dev Containers``` and install it. 6. Once completed with the prior steps you may run command ```>Dev Containers: Open Workspace in Container``` or ```>Dev Containers: Open Folder in Container``` which you can do in the command bar in the IDE and it should reopen the visual studio code project. 7. It will launch a docker and start building a new docker and then the project will open in visual studio code. #### Build & Compile Options 1. Assuming your VSCode IDE is open and the dev container is working. 2. Go to the CMake plugin you may select the "Folder" that is where you currently want to build. 3. Once done with that you may select which building toolkit you are using under Configure and then begin configure. 4. Under Build, Test, Debug and/or Launch you may select whatever configuration you want to build, test, debug and/or launch. #### Debug / Runtime / Console arguments 1. You may find arguments which are passed through to the application you are debugging in .vscode/settings.json which will look like the following: ``` "cmake.debugConfig": { "args": [ "--llamacpp", "cpu" ] } ``` 2. If you want to debug lemonade-router you may pass --llamacpp cpu for cpu based tests. 3. For lemonade-server you may pass serve as a argument as well. ##### The hard way - commands only. 1. Now if you want to do it the hard way below are the commands in which you can run in the command dropdown in which you can see if you use the following keyboard shortcuts. cmd + p / control + p ``` > Cmake: Select a Kit # Select a kit or Scan for kit. (Two options should be available gcc or clang) > Cmake: Configure # Optional commands are: > Cmake: Build Target # use this to select a cmake target to build > Cmake: Set Launch/Debug target # use this to select/set your cmake target you want to build/debug # This next command lets you debug > Cmake: Debug # This command lets you delete the cmake cache and reconfigure which is rarely needed. > Cmake: Delete Cache and Reconfigure ``` 2. Custom configurations for cmake are in the root directory under ```.vscode/settings.json``` in which you may set custom args for launching the debug in the json key ```cmake.debugConfig``` > **Note** > > For running Lemonade as a containerized application (as an alternative to the MSI-based distribution), see `DOCKER_GUIDE.md`. ## Code Structure ``` src/cpp/ ├── build_installer.ps1 # Installer build script ├── CopyElectronApp.cmake # CMake module to copy Electron app to build output ├── CPackRPM.cmake # RPM packaging configuration ├── DOCKER_GUIDE.md # Docker containerization guide ├── Extra-Models-Dir-Spec.md # Extra models directory specification ├── Multi-Model-Spec.md # Multi-model loading specification ├── postinst # Debian package post-install script ├── postinst-full # Debian package post-install script (full version) ├── resources/ # Configuration and data files (self-contained) │ ├── backend_versions.json # llama.cpp/whisper version configuration │ ├── server_models.json # Model registry (available models) │ └── static/ # Web UI assets │ ├── index.html # Server landing page (with template variables) │ └── favicon.ico # Site icon │ ├── installer/ # WiX MSI installer (Windows) │ ├── Product.wxs.in # WiX installer definition template │ ├── installer_banner_wix.bmp # Left-side banner (493×312) │ └── top_banner.bmp # Top banner with lemon icon (493×58) │ ├── server/ # Server implementation │ ├── main.cpp # Entry point, CLI routing │ ├── server.cpp # HTTP server (cpp-httplib) │ ├── router.cpp # Routes requests to backends │ ├── model_manager.cpp # Model registry, downloads, caching │ ├── cli_parser.cpp # Command-line argument parsing (CLI11) │ ├── recipe_options.cpp # Recipe option handling │ ├── wrapped_server.cpp # Base class for backend wrappers │ ├── streaming_proxy.cpp # Server-Sent Events for streaming │ ├── system_info.cpp # NPU/GPU device detection │ ├── lemonade.manifest.in # Windows manifest template │ ├── version.rc # Windows version resource │ │ │ ├── backends/ # Model backend implementations │ │ ├── backend_utils.cpp # Shared backend utilities │ │ ├── llamacpp_server.cpp # Wraps llama.cpp for LLM inference (CPU/GPU) │ │ ├── fastflowlm_server.cpp # Wraps FastFlowLM for NPU inference │ │ ├── ryzenaiserver.cpp # Wraps RyzenAI server for hybrid NPU │ │ ├── sd_server.cpp # Wraps Stable Diffusion for image generation │ │ └── whisper_server.cpp # Wraps whisper.cpp for audio transcription (CPU/NPU) │ │ │ └── utils/ # Utility functions │ ├── http_client.cpp # HTTP client using libcurl │ ├── json_utils.cpp # JSON file I/O │ ├── process_manager.cpp # Cross-platform process management │ ├── path_utils.cpp # Path manipulation │ ├── wmi_helper.cpp # Windows WMI for NPU detection │ └── wmi_helper.h # WMI helper header │ ├── include/lemon/ # Public headers │ ├── server.h # HTTP server interface │ ├── router.h # Request routing │ ├── model_manager.h # Model management │ ├── cli_parser.h # CLI argument parsing │ ├── recipe_options.h # Recipe option definitions │ ├── wrapped_server.h # Backend wrapper base class │ ├── streaming_proxy.h # Streaming proxy │ ├── system_info.h # System information │ ├── model_types.h # Model type definitions │ ├── audio_types.h # Audio type definitions │ ├── error_types.h # Error type definitions │ ├── server_capabilities.h # Server capability definitions │ ├── single_instance.h # Single instance enforcement │ ├── version.h.in # Version header template │ ├── backends/ # Backend headers │ │ ├── backend_utils.h # Backend utilities │ │ ├── llamacpp_server.h # LlamaCpp backend │ │ ├── fastflowlm_server.h # FastFlowLM backend │ │ ├── ryzenaiserver.h # RyzenAI backend │ │ ├── sd_server.h # Stable Diffusion backend │ │ └── whisper_server.h # Whisper backend │ └── utils/ # Utility headers │ ├── http_client.h # HTTP client │ ├── json_utils.h # JSON utilities │ ├── process_manager.h # Process management │ |── path_utils.h # Path utilities | |── network_beacon.h # Helps broadcast a beacon on port 8000 to network multicast │ └── tray/ # System tray application ├── CMakeLists.txt # Tray-specific build config ├── main.cpp # Tray entry point (lemonade-server) ├── tray_launcher.cpp # GUI launcher (lemonade-tray) ├── log-viewer.cpp # Log file viewer (lemonade-log-viewer) ├── server_manager.cpp # Manages lemonade-router process ├── tray_app.cpp # Main tray application logic ├── lemonade-server.manifest.in # Windows manifest template ├── version.rc # Windows version resource └── platform/ # Platform-specific implementations ├── windows_tray.cpp # Win32 system tray API ├── macos_tray.mm # Objective-C++ NSStatusBar ├── linux_tray.cpp # GTK/AppIndicator └── tray_factory.cpp # Platform detection ``` ## Architecture Overview ### Overview The Lemonade Server C++ implementation uses a client-server architecture: #### lemonade-router (Server Component) A pure HTTP server that: - Serves OpenAI-compatible REST API endpoints (supports both `/api/v0` and `/api/v1`) - Routes requests to appropriate LLM backends (llamacpp, fastflowlm, ryzenai) - Manages model loading/unloading and backend processes - Supports loading multiple models simultaneously with LRU eviction - Handles all inference requests - No command-based user interface - only accepts startup options **Key Layers:** - **HTTP Layer:** Uses cpp-httplib for HTTP server - **Router:** Determines which backend handles each request based on model recipe, manages multiple WrappedServer instances with LRU cache - **Model Manager:** Handles model discovery, downloads, and registry management - **Backend Wrappers:** Manages llama.cpp, FastFlowLM, RyzenAI, and whisper.cpp backends **Multi-Model Support:** - Router maintains multiple WrappedServer instances simultaneously - Separate LRU caches for LLM, embedding, reranking, and audio model types - NPU exclusivity: only one model can use NPU at a time - Configurable limits via `--max-loaded-models N` (default: 1) - Automatic eviction of least-recently-used models when limits reached - Thread-safe model loading with serialization to prevent races - Protection against evicting models actively serving inference requests #### lemonade-server (CLI Client Component) A console application for terminal users: - Provides command-based user interface (`list`, `pull`, `delete`, `run`, `status`, `stop`, `serve`) - Manages server lifecycle (start/stop persistent or ephemeral servers) - Communicates with `lemonade-router` via HTTP endpoints - Starts `lemonade-router` with appropriate options - Provides optional system tray interface via `serve` command **Command Types:** - **serve:** Starts a persistent server (with optional tray interface) - **run:** Starts persistent server, loads model, opens browser - **Other commands:** Use existing server or start ephemeral server, execute command via API, auto-cleanup #### lemonade-tray (GUI Launcher - Windows Only) A minimal WIN32 GUI application for desktop users: - Simple launcher that starts `lemonade-server.exe serve` - Zero console output or CLI interface - Used by Start Menu, Desktop shortcuts, and autostart - Provides seamless GUI experience for non-technical users ### Client-Server Communication The `lemonade-server` client communicates with `lemonade-router` server via HTTP: - **Model operations:** `/api/v1/models`, `/api/v1/pull`, `/api/v1/delete` - **Model control:** `/api/v1/load`, `/api/v1/unload` - **Server management:** `/api/v1/health`, `/internal/shutdown` - **Inference:** `/api/v1/chat/completions`, `/api/v1/completions`, `/api/v1/audio/transcriptions` The client automatically: - Detects if a server is already running - Starts ephemeral servers for one-off commands - Cleans up ephemeral servers after command completion - Manages persistent servers with proper lifecycle handling **Single-Instance Protection:** - Each component (`lemonade-router`, `lemonade-server serve`, `lemonade-tray`) enforces single-instance using system-wide mutexes - Only the `serve` command is blocked when a server is running - Commands like `status`, `list`, `pull`, `delete`, `stop` can run alongside an active server - Provides clear error messages with suggestions when blocked - **Linux-specific:** Uses a PID file (`lemonade-router.pid`) for efficient server discovery and port detection - Stored in `$XDG_RUNTIME_DIR/lemonade/` when the XDG runtime directory is set and writable, otherwise falls back to `/tmp/` - Avoids port scanning, finds exact server PID and port instantly - Validated on read (checks if process is still alive) - Automatically cleaned up on graceful shutdown **Network Beacon based broadcasting:** - Uses port 8000 to broadcast to the network that it exists - Clients can read the json broadcast message to add server to server picker. - Uses machine hostname as broadcast name. - The custom flag --no-broadcast is available in the command line to disable. - Auto protection, doesnt broadcast on non RFC1918 Networks. ### Dependencies All dependencies are automatically fetched by CMake via FetchContent: - **cpp-httplib** (v0.26.0) - HTTP server with thread pool support [MIT License] - **nlohmann/json** (v3.11.3) - JSON parsing and serialization [MIT License] - **CLI11** (v2.4.2) - Command-line argument parsing [BSD 3-Clause] - **libcurl** (8.5.0) - HTTP client for model downloads [curl license] - **zstd** (v1.5.7) - Compression library for HTTP [BSD License] Platform-specific SSL backends are used (Schannel on Windows, SecureTransport on macOS, OpenSSL on Linux). ## Usage ### lemonade-router (Server Only) The `lemonade-router` executable is a pure HTTP server without any command-based interface: ```bash # Start server with default options ./lemonade-router # Start server with custom options ./lemonade-router --port 8080 --ctx-size 8192 --log-level debug # Available options: # --port PORT Port number (default: 8000) # --host HOST Bind address (default: localhost) # --ctx-size SIZE Context size (default: 4096) # --log-level LEVEL Log level: critical, error, warning, info, debug, trace # --llamacpp BACKEND LlamaCpp backend: vulkan, rocm, metal # --max-loaded-models N Maximum models per type slot (default: 1) # --version, -v Show version # --help, -h Show help ``` ### lemonade-server.exe (Console CLI Client) The `lemonade-server` executable is the command-line interface for terminal users: - Command-line interface for all model and server management - Starts persistent servers (with optional tray interface) - Manages ephemeral servers for one-off commands - Communicates with `lemonade-router` via HTTP endpoints ```bash # List available models ./lemonade-server list # Pull a model ./lemonade-server pull Llama-3.2-1B-Instruct-CPU # Delete a model ./lemonade-server delete Llama-3.2-1B-Instruct-CPU # Check server status ./lemonade-server status # Stop the server ./lemonade-server stop # Run a model (starts persistent server with tray and opens browser) ./lemonade-server run Llama-3.2-1B-Instruct-CPU # Start persistent server (with tray on Windows/macOS, headless on Linux) ./lemonade-server serve # Start persistent server without tray (headless mode, explicit on all platforms) ./lemonade-server serve --no-tray # Start server with custom options ./lemonade-server serve --port 8080 --ctx-size 8192 ``` **Available Options:** - `--port PORT` - Server port (default: 8000) - `--host HOST` - Server host (default: localhost) - `--ctx-size SIZE` - Context size (default: 4096) - `--log-level LEVEL` - Logging verbosity: info, debug (default: info) - `--log-file PATH` - Custom log file location - `--server-binary PATH` - Path to lemonade-router executable - `--no-tray` - Run without tray (headless mode) - `--max-loaded-models N` - Maximum number of models to keep loaded per type slot (default: 1) **Note:** `lemonade-router` is always launched with `--log-level debug` for optimal troubleshooting. Use `--log-level debug` on `lemonade-server` commands to see client-side debug output. ### lemonade-tray.exe (GUI Tray Launcher - Windows Only) The `lemonade-tray` executable is a simple GUI launcher for desktop users: - Double-click from Start Menu or Desktop to start server - Automatically runs `lemonade-server.exe serve` in tray mode - Zero console windows or CLI interface - Perfect for non-technical users - Single-instance protection: shows friendly message if already running **What it does:** 1. Finds `lemonade-server.exe` in the same directory 2. Launches it with the `serve` command 3. Exits immediately (server continues running with tray icon) **When to use:** - Launching from Start Menu - Desktop shortcuts - Windows startup - Any GUI/point-and-click scenario **System Tray Features (when running):** - Left-click or right-click icon to show menu - Load/unload models via menu - Change server port and context size - Open web UI, documentation, and logs - "Show Logs" opens log viewer with historical and live logs - Background model monitoring - Click balloon notifications to open menu - Quit option **UI Improvements:** - Displays as "Lemonade Local LLM Server" in Task Manager - Shows large lemon icon in notification balloons - Single-instance protection prevents multiple tray apps ### Logging and Console Output When running `lemonade-server.exe serve`: - **Console Output:** Router logs are streamed to the terminal in real-time via a background tail thread - **Log File:** All logs are written to a persistent log file (default: `%TEMP%\lemonade-server.log`) - **Log Viewer:** Click "Show Logs" in the tray to open `lemonade-log-viewer.exe` - Displays last 100KB of historical logs - Live tails new content as it's written - Automatically closes when server stops - Uses shared file access (won't block installer) **Log Viewer Features:** - Cross-platform tail implementation - Parent process monitoring for auto-cleanup - Installer-friendly (FILE_SHARE_DELETE on Windows) - Real-time updates with minimal latency (100ms polling) ## Testing ### Basic Functionality Tests Run the commands from the Usage section above to verify basic functionality. ### Integration Tests The C++ implementation is tested using the existing Python test suite. **Prerequisites:** - Python 3.10+ - Test dependencies: `pip install -r test/requirements.txt` **Python integration tests** (from `test/` directory, ordered least to most complex): | Test File | Description | |-----------|-------------| | `server_cli.py` | CLI commands (version, list, pull, status, delete, serve, stop, run) | | `server_endpoints.py` | HTTP endpoints (health, models, pull, load, unload, system-info, stats) | | `server_llm.py` | LLM inference (chat completions, embeddings, reranking) | | `server_whisper.py` | Audio transcription (whisper models) | | `server_sd.py` | Image generation (Stable Diffusion, ~2-3 min per image on CPU) | **Running tests:** ```bash # CLI tests (no inference backend needed) python test/server_cli.py # Endpoint tests (no inference backend needed) python test/server_endpoints.py # LLM tests (specify wrapped server and backend) python test/server_llm.py --wrapped-server llamacpp --backend vulkan # Audio transcription tests python test/server_whisper.py # Image generation tests (slow) python test/server_sd.py ``` The tests auto-discover the server binary from the build directory. Use `--server-binary` to override if needed. See the `.github/workflows/` directory for CI/CD test configurations. **Note:** The Python tests should now use `lemonade-server.exe` as the entry point since it provides the CLI interface. ## Development ### Code Style - C++17 standard - Snake_case for functions and variables - CamelCase for classes and types - 4-space indentation - Header guards using `#pragma once` - All code in `lemon::` namespace ### Key Resources - **API Specification:** `docs/server/server_spec.md` - **Model Registry:** `src/cpp/resources/server_models.json` - **Web UI Files:** `src/cpp/resources/static/` - **Backend Versions:** `src/cpp/resources/backend_versions.json` ## License This project is licensed under the Apache 2.0 License. All dependencies use permissive licenses (MIT, BSD, Apache 2.0, curl license). lemonade-sdk-lemonade-d88f5d9/docs/driver_install.html000066400000000000000000000177221515430344400231470ustar00rootroot00000000000000 NPU Driver Installation - Lemonade Server
NPU Driver Installation
Install the required NPU driver for AMD Ryzen AI processors

Why do I need the NPU driver?

Lemonade uses Ryzen AI Software and FastFlowLM software to access the NPU (Neural Processing Unit) on AMD Ryzen AI processors. New releases of these engines often require an updated NPU driver. The NPU driver is required for AI acceleration and enables hardware-accelerated inference for machine learning models on compatible systems.

Note: The minimum required NPU driver version for RyzenAI 1.6.0 is 32.0.203.280 and the FastFlowLM recommended driver version is provided here.

Installation Options

Option A Recommended

Use AMD's Driver Download Tool

Visit AMD's official driver download page to automatically detect and install the correct NPU driver for your system:

Go to AMD Driver Downloads

Follow the on-screen instructions to detect your hardware and download the appropriate driver.

Option B If Option A doesn't work

Manual Driver Installation

If Option A doesn't work or you need a specific driver version, follow these manual steps:

1

Download the NPU Driver

Click the button below to download the NPU driver directly:

Download NPU Driver
2

Install the Driver

Unzip the downloaded folder. Then, open an Administrator Command Prompt, navigate to the unzipped folder, and run npu_sw_installer.exe. Follow the installation wizard to complete the process.

3

Verify Installation

Try Lemonade Server again to verify the NPU driver is working correctly.

Need Help?

If you continue to experience issues after following these steps:

lemonade-sdk-lemonade-d88f5d9/docs/faq.md000066400000000000000000000313651515430344400203300ustar00rootroot00000000000000# 🍋 Lemonade Frequently Asked Questions ## Overview ### 1. **What is Lemonade and what does it include?** Lemonade is an open-source local LLM solution that: - Gets you started in minutes with one-click installers. - Auto-configures optimized inference engines for your PC. - Provides a convenient app to get set up and test out LLMs. - Provides LLMs through the OpenAI API standard, enabling apps on your PC to access them. ### 2. **What are the use cases for different audiences?** - **LLM Enthusiasts**: LLMs on your GPU or NPU with minimal setup, and connect to great apps listed [here](https://lemonade-server.ai/docs/server/apps/). - **Developers**: Integrate LLMs into apps using standard APIs with no device-specific code. See the [Server Integration Guide](https://lemonade-server.ai/docs/server/server_integration/ ). - **Agent Developers**: Use [GAIA](https://github.com/amd/gaia) to quickly develop local-first agents. ## Installation & Compatibility ### 1. **How do I install Lemonade SDK or Server?** Visit https://lemonade-server.ai/install_options.html and click the options that apply to you. ### 2. **Which devices are supported?** 👉 [Supported Configurations](https://github.com/lemonade-sdk/lemonade?tab=readme-ov-file#supported-configurations) For more information on AMD Ryzen AI NPU Support, see the section [Hybrid/NPU](#hybrid-and-npu-questions). ### 3. **Is Linux supported? What about macOS?** Yes, both Linux and macOS are supported! - **Linux**: Visit https://lemonade-server.ai/install_options.html#linux for installation instructions. - **macOS (beta)**: A macOS installer (.pkg) is available for Apple Silicon Macs. Visit https://lemonade-server.ai/install_options.html#macos to download. macOS support uses the llama.cpp backend with Metal acceleration. Visit the [Supported Configurations](https://github.com/lemonade-sdk/lemonade?tab=readme-ov-file#supported-configurations) section to see the support matrix for CPU, GPU, and NPU. ### 4. **How do I uninstall Lemonade Server? (Windows)** To uninstall Lemonade Server, use the Windows Add/Remove Programs menu. **Optional: Remove cached files** - Open File Explorer and navigate to `%USERPROFILE%\.cache` - Delete the `lemonade` folder if it exists - To remove downloaded models, delete the `huggingface` folder ## Models ### 1. **Where are models stored and how do I change that?** Lemonade uses three model locations: **Primary: Hugging Face Cache** Models downloaded through Lemonade are stored using the Hugging Face Hub specification. By default, models are located at `~/.cache/huggingface/hub/`, where `~` is your home directory. For example, `Qwen/Qwen2.5-0.5B` is stored at `~/.cache/huggingface/hub/models--Qwen--Qwen2.5-0.5B`. You can change this location by setting the `HF_HOME` env var, which will store your models in `$HF_HOME/hub` (e.g., `$HF_HOME/hub/models--Qwen--Qwen2.5-0.5B`). Alternatively, you can set `HF_HUB_CACHE` and your models will be in `$HF_HUB_CACHE` (e.g., `$HF_HUB_CACHE/models--Qwen--Qwen2.5-0.5B`). You can use the official Hugging Face Hub utility (`pip install huggingface-hub`) to manage models outside of Lemonade, e.g., `hf cache ls` will print all models and their sizes. **Secondary: Extra Models Directory (GGUF)** Lemonade Server can discover GGUF models from a secondary directory using the `--extra-models-dir` option, enabling compatibility with llama.cpp and LM Studio model caches. Suggested paths: - **Windows:** - LM Studio: `C:\Users\You\.lmstudio\models` - llamacpp: `%LOCALAPPDATA%\llama.cpp` (e.g., `C:\Users\You\AppData\Local\llama.cpp`) - **Linux:** `~/.cache/llama.cpp` Example: `lemonade-server serve --extra-models-dir "%LOCALAPPDATA%\llama.cpp"` Any `.gguf` files found in this directory (including subdirectories) will automatically appear in Lemonade's model list in the `custom` category. **FastFlowLM** FastFlowLM (FLM) has its own model management system. When you first install FLM the install wizard asks for a model directory, which is then saved to the `FLM_MODEL_PATH` environment variable on your system PATH. Models are stored in that directory. If you change the variable's value, newly downloaded models will be stored on the new path, but your prior models will still be at the prior path. ### 2. **What models are supported?** Lemonade supports a wide range of LLMs including LLaMA, DeepSeek, Qwen, Gemma, Phi, gpt-oss, LFM, and many more. Most GGUF models can also be added to Lemonade Server by users using the Model Manager interface in the app or the `pull` command on the CLI. 👉 [Supported Models List](https://lemonade-server.ai/models.html) 👉 [pull command](https://lemonade-server.ai/docs/server/lemonade-server-cli/#options-for-pull) ### 3. **How do I know what size model will work with my setup?** Model compatibility depends on your system's RAM, VRAM, and NPU availability. **The actual file size varies significantly between models** due to different quantization techniques and architectures. **To check if a model will work:** 1. Visit the model's Hugging Face page (e.g., [`amd/Qwen2.5-7B-Chat-awq-g128-int4-asym-fp16-onnx-hybrid`](https://huggingface.co/amd/Qwen2.5-7B-Chat-awq-g128-int4-asym-fp16-onnx-hybrid)). 2. Check the "Files and versions" tab to see the actual download size. 3. Add ~2-4 GB overhead for KV cache, activations, and runtime memory. 4. Ensure your system has sufficient RAM/VRAM. ### 4. **I'm looking for a model, but it's not listed in the Model Manager.** If a model isn't listed, it may not be compatible with your PC due to device or RAM limitations, or we just haven't added it to the `server_models.json` file yet. You can: - Add a custom model manually via the app's "Add a Model" interface or the [CLI pull command](https://lemonade-server.ai/docs/server/lemonade-server-cli/#options-for-pull). For advanced manual configuration, see the [Custom Model Configuration Guide](https://lemonade-server.ai/docs/server/custom-models/). - Use a pull request to add the model to the built-in `server_models.json` file. - Request support by opening a [GitHub issue](https://github.com/lemonade-sdk/lemonade/issues). If you are sure that a model should be listed, but you aren't seeing it, you can set the `LEMONADE_DISABLE_MODEL_FILTERING` environment variable to show all models supported by Lemonade on any PC configuration. But please note, this can show models that definitely won't work on your system. Alternatively if you are attempting to use GTT on your dGPU then you can set the `LEMONADE_ENABLE_DGPU_GTT` environment variable to filter using the combined memory pool. Please note ROCM does not support splitting memory across multiple pools, vulkan is likely required for this usecase. ### 5. **Is there a script or tool to convert models to Ryzen AI NPU format?** Yes, there's a guide on preparing your models for Ryzen AI NPU: 👉 [Model Preparation Guide](https://ryzenai.docs.amd.com/en/latest/oga_model_prepare.html) ### 6. **What's the difference between GGUF and ONNX models?** - **GGUF**: Used with llama.cpp backend, supports CPU, and GPU via Vulkan or ROCm. - **ONNX**: Used with OnnxRuntime GenAI, supports NPU and NPU+iGPU Hybrid execution. ## Inference Behavior & Performance ### 1. **Can Lemonade print out stats like tokens per second?** Yes! Lemonade Server exposes a `/stats` endpoint that returns performance metrics from the most recent completion request: ```bash curl http://localhost:8000/api/v1/stats ``` Or, you can launch `lemonade-server` with the option `--log-level debug` and that will also print out stats. ### 2. **How does Lemonade's performance compare to llama.cpp?** Lemonade supports llama.cpp as a backend, so performance is similar when using the same model and quantization. ### 3. **How can ROCm performance be improved for my use case?** File a detailed issue on TheRock repo for support: https://github.com/ROCm/TheRock ### 4. **How should dedicated GPU RAM be allocated on Strix Halo** **Linux** Please see the official AMD guidance [here](https://rocm.docs.amd.com/en/latest/how-to/system-optimization/strixhalo.html). **Windows** Strix Halo PCs can have up to 128 GB of unified RAM and Windows allows the user to allocate a portion of this to dedicated GPU RAM. We suggest setting dedicated GPU RAM to `64/64 (auto)`. > Note: On Windows, the GPU can access both unified RAM and dedicated GPU RAM, but the CPU is blocked from accessing dedicated GPU RAM. For this reason, allocating too much dedicated GPU RAM can interfere with model loading, which requires the CPU to access a substantial amount unified RAM. ## Hybrid and NPU Questions ### 1. **Does LLM inference with the NPU only work on Windows?** Yes, today, NPU and hybrid inference is currently supported only on Windows. To request NPU support on Linux, file an issue with either: - Ryzen AI SW: https://github.com/amd/ryzenai-sw - FastFlowLM: https://github.com/FastFlowLM/FastFlowLM ### 2. **I loaded a hybrid model, but the NPU is barely active. Is that expected?** Yes. In hybrid mode: - The NPU handles prompt processing. - The GPU handles token generation. - If your prompt is short, the NPU finishes quickly. Try a longer prompt to see more NPU activity. ### 3. **Does Lemonade work on older AMD processors or non-Ryzen AI systems?** Yes! Lemonade supports multiple execution modes: - **AMD Ryzen 7000/8000/200 series**: GPU acceleration via llama.cpp + Vulkan backend - **Systems with Radeon GPUs**: Yes - **Any x86 CPU**: Yes - **Intel/NVIDIA systems**: CPU inference, with GPU support via the llama.cpp + Vulkan backend While you won't get NPU acceleration on non-Ryzen AI 300 systems, you can still benefit from GPU acceleration and the OpenAI-compatible API. ### 4. **Is the NPU on the AMD Ryzen AI 7000/8000/200 series going to be supported for LLM inference?** No inference engine providers have plans to support NPUs prior to Ryzen AI 300-series, but you can still request this by filing an issue on their respective GitHubs: - Ryzen AI SW: https://github.com/amd/ryzenai-sw - FastFlowLM: https://github.com/FastFlowLM/FastFlowLM ### 5. **How do I know what model architectures are supported by the NPU?** AMD publishes pre-quantized and optimized models in their Hugging Face collections: - [Ryzen AI NPU Models](https://huggingface.co/collections/amd/ryzenai-15-llm-npu-models-6859846d7c13f81298990db0) - [Ryzen AI Hybrid Models](https://huggingface.co/collections/amd/ryzenai-15-llm-hybrid-models-6859a64b421b5c27e1e53899) To find the architecture of a specific model, click on any model in these collections and look for the "Base model" field, which will show you the underlying architecture (e.g., Llama, Qwen, Phi). ### 6. **How can I get better performance from the NPU?** Make sure that you've put the NPU in "Turbo" mode to get the best results. This is done by opening a terminal window and running the following commands: ```cmd cd C:\Windows\System32\AMD .\xrt-smi configure --pmode turbo ``` ## Remote Access ### 1. **How do I run Lemonade Server on one PC and access it from another?** Lemonade supports running the server on one machine while using the app from another machine on the same network. **Quick setup:** 1. On the server machine, start with network access enabled: ```bash lemonade-server serve --host 0.0.0.0 --port 8000 ``` 2. On the client machine, launch the app and configure the endpoint through the UI: ```bash lemonade-app ``` For detailed instructions and security considerations, see [Remote Server Connection](./lemonade-server-cli.md#remote-server-connection). ## Customization ### 1. **How do I use my own llama.cpp or whisper.cpp binaries?** Lemonade Server allows you to use custom `llama-server` or `whisper-server` binaries instead of the bundled ones by setting environment variables to the full path of your binary. 👉 [Custom Backend Binaries](./server/lemonade-server-cli.md#custom-backend-binaries) ## Support & Roadmap ### 1. **What if I encounter installation or runtime errors?** Check the Lemonade Server logs via the App (all supported OSes) or tray icon (Windows only). Common issues include model compatibility or outdated versions. 👉 [Open an Issue on GitHub](https://github.com/lemonade-sdk/lemonade/issues) ### 2. **Lemonade is missing a feature I really want. What should I do?** Open a feature request on GitHub. We're actively shaping the roadmap based on user feedback. ### 3. **Do you plan to share a roadmap?** Yes! Check out the project README: 👉 [Lemonade Roadmap](https://github.com/lemonade-sdk/lemonade#project-roadmap) lemonade-sdk-lemonade-d88f5d9/docs/favicon.ico000066400000000000000000003674311515430344400213660ustar00rootroot00000000000000 hf  00 %v@@ (B; (F} in(  ~ m>n8PphU@DF|upk_KBB@1tjqlJpojd^WSUẉYl WqYXajke`YMB?@`@O`Qz_f`ZXQD701ص: FCP`^TPQMA4--5BA@P^TIJPQH:009==ZMWIAJW[RB54H66NBK>GMKNMf%ȝ_4ԫLVŬK3<?>?A">$=)΢ 9۲P @tܺ( @ ~dPԜ&yngg?!ps}Ar{i_RHD!E֑jȀzvttodZK94U4+zsnkhfdca\VIAɀ6Xqww-vronlifc`]ZXXZx^rgk&entajkkkmonlheb_[VRNMRzXr¯ZlYna`acimnlifca]XRMGCBEJxuEKt]XX?Zbiljgca`^ZUNGA<9872/-,2ϥNx<NSIQ_dc^YTRRRQOJC<61.,+0;:GF GT`b_YSOMNOPNKE>72/-,0;;BBRFW``ZSMJJLNPPNIB;51.-2?=H:?GX^\UMGEGKOSTSNG@93/.4IBFgp=GW\XPGBBFLRVYXSMD=6108ICI:DTXSJB>@FOW\^]XPH?833=Q:B7>MRME=:?HS]ceb\SJA:48DCA.5{7CIF>87>JXcjlh`VLC;7=K45C18>=845=K[hpqmeZOD;;D1B890/34202:IZhqtph]QE>A`L>+2^++---/6CSbmqph^RFCH(Gamne_ ? -'*,-.1;HWdkkf]QHI_:R]vhKLNpQO=% ùR&(/0015>JW_c`WNLSPG>?!=;<@>ť0ɧ̧β/87559AJRVTONyQ P23 0+,1Š9ɦ?Ϋ?Ѯ5԰'װٴ 5?<98;@EIKNGSQ*, "~!%ś,ˢ8ЩBհGٵEܷ?޹94/ĄBLA=:;>CG@L GU'*$#$×x$ɞ,ѧ>ٲPXZXQŌE HSFCDKwj;fk,×)˟j.ԩ=ݴOYŤZLUWJ0ȝ +Ц.Ӫ'ʢ2ٲa0??(0` $U zSH{squ~Or~vj9tia[Y]zar^yϙGƏяҎՆtgb]SFBD4D!z͇|xvvvvqf^XNA66m3 bրytpnlkjiiie_XQE74̓=~vromkigfecba`_\Y|V|KC}ȾHu Iw}~|vqponljhfeca_]\ZYY[z^u_sdoOXvpgst spmnoonmjhfdb`^[YVTRSU|Zubofj1cmzYklkwiiknoonljhfdb_]ZVSPMLKLRwYq[lZmrcdBccglnonljhfdca^\XTPLIFDCDJ{OtWeRp^_]^bhlmmkigecba_]ZVRMIEB?=<=C~Hx5Gy]UXjX]dikkjheca`_^]ZWSOJEA=:8669?YaxD|UUSV^eijifda_^]\[ZXTPLGB=9642118>!=VKPrPX`ehgec_][ZYYXWUQMIC?:631/.-19Y6EPPMPZbefeb_[XWVVUUTROKFA<841/-,+.5ɐAQIK]JS]cedb^[WTSRSSSRPMID@;730/-,+,3жC=MPGJU^cdb_[WSQPOPPQPOLIE@;731/-,+,2?=HH,DLX_bb_[WSOMMMNOPPOMJFB=9520.-,,3@?ADdCNY_a`\XSOLJJKLNOPPOLIE@;741/-,-4CAJ@hACPZ_`]YTOKHGHJLNPQRQPMHC?:630.-.6LDFG ?CQZ^^ZUPKGEEFILORTUUSPLGB=951/.09KCB=CQY\[WRLGCBCEHLPTVXXVTPKE@;730/2=k6L@@$;BOXZYTOIC@?ADINSWZ[[ZWSMHB=84106B8@?>&9@MUWUQKE@==@EKQV[^__]ZUPJD>:622:JG>>7?57CKNMIC=98;@GPX_dhihd`ZSMFA<75:E5C@U53:6448?IS^flpqokf_WPIB=8;ENA89*1/489863126>HS^gnrsrnhaZRJC=:AO K=K3-.12210/04?GMBF47;.*,...--.28ALXbjprrojc\TLD@ETO;H2*(*,,,,-/4= ?<658<¡@ť@ȧ9ʩ*ͩΨϧϵ,9:875569>DJOSTSONOpUR7:l9r3,+.3š8Ȥ<ʨ?ͫ>ϭ:Ѯ0ԯ%կ׮س.>?=:878;>BFIJKLO?YT.0)"$)Û-Ɵ1ɣ7̧=ϫAҮBձA׳=ش7ڴ/ܶ(߷!"1BA>;989;>ADHKRP MX#%}= "Ś'ɟ-̤5Щ>ԮDײHڵIܸG޹Dߺ@<;9/3I C8Bn@=<<>ADYI'OJ[+0'4$"ƚ#ˠ*ѧ9֮F۵OߺTUUVWSKI\K۵QXJG H Rt8D.*(Ś%ˠ)ҧ6ڰHW`cb]ɱU5Jس@K3ę!-ˠm,ҧ1خ<޵GM侓MHFڵ OJ=r{<ě/̢,Ϧ+̤ ????(@ @[w|ȕJf~yy~~Pu ||E Tsic`bj~ۧqw k{cϑ 0=BAB`znhd_XPOUԩYy W|Mܐ쎔}ogc`\TJA?CIC-ь}zxwwxxwpg`\XPF<57̈́,>ㅭ~yusqonmmllmlg`[VPG<44ͫCy:J̃{vspnmkjihgffeeda]YUOD95<~;nzurpomljigfedcba``_^\~[|Z{W{M}D|Ex2Ey|{tqpoonlkihfedcb`_^\[ZZZ[{]w`t^t_q|^jjyzxtupnnooonmkjhfedba_]\ZXVUUUW|Zw_seoikXOkftjrZolkmnooonmkihfecb`_\ZXVTRPPPPSyYt`nck?bmdgnkl4jghjmoooonljigfdca`^\YVTQOMLJJJM|RvYp[l [mfgeccgjmnoonlkigfecba_]ZXUROMJHFEDDFMxRrVkTod6as__cgkmnnmljhgedcba`^\YVSPMJGDBA?>?BHzLuGKwPo]].[[_dhklmlkigfdcba`_^\ZXTQNJGDA?=;:9:>D|It GwZ]XWZ`ehkkkjhfdca``_^^\[XVROKHDA><986557SU[aehjjigeca`_^]]\[ZXVSPMIEB>;96532116:7420/-,+++.5ˠdr>PCJtGMV]addca^\YVSRQPQQQQQPONKHEA>:7420/.-,++-4кD>MOGFOX^bcca_\YUSQOONOOPPPPOMKIFB?;8521/.-,++-4C@HI9DGQY_bba_]YVRPNMLLMNNOPPONLJGD@=96420/--,,.5DBN>EuBISZ_aa`]ZVSPMLKJKLMNOPPPONLIFC?;8631/.-,,.6G CJPBAJT[_``^[WSPMKIHIJKLNOPQQQPNLIEB>:7520/.--/8ݵNDGG@ALU[^_^\XTPMJHGFGHJLNPQRSSRQNKHDA=96410..-0;ޚ&HCD2>BLU[^^]ZVRNJGEDEFGJLOQSTUUUSQNKGC?<8531/..3>u5P@AK:7420//6AJ?<?_;ALTY[[XUQLHDB@@BDGKNQTWYZZZXVSPLHC?;8531009G!E:>i9@JRWYYVRNIEA?>?ADHLPSWY[]]\[XURNIEA=964103=UK9=h8>HPUWVSPKGB?==>@DIMRVZ]_``_]ZWSOKFB>:74216Bl8Q9<\6;EMRTSQMHD@=;;=@EJOTY]`bccb`]YTPLGC?;8533;H,F;:879@517>CFGEB?;8667;AGOV]bgkmnmkhd_ZTOJEA=977@NKA6028=@A@>;86446:@HOW_ejnpqpnjfa\VQKFA=97;Eo8O68M1/37:<;:8532259@GOX_flprsrpmhc^XRMGB>:9AMJ<=3-/256664210148>FNW_flpstsqnjd_YSNHC>;>Gt*U>)6r.,.012210//026Ť@Ǧ?ɨ<˩4ͪ'ΪЪШϧϷ)6;:98766679=AEINQRSROMMPeWT6=;<91,+-0ž3ġ7Ǥ;ɦ>˩?ͪ>ά;Э6Ү-Ӯ$ծ֮׭ײ);?>=;98788:=@DGIKKKKMQ6eW243*%'),Ý/Ơ2ȣ6ʥ:ͨ>ϫ@ѭ@ү@԰=ձ8ֲ3ز,ٳ%۴ݵ߶&:A@?=:9889;=?ACEGJNWS Ne*,##'ě*Ǟ-ɡ1ˤ6ͧ;Ы?ҮB԰DֲDشCٵ@ڶ<۶8ݷ4޸/+)'-kD\BA?=;988:<>ADGKCQ =|& !W  ę#ȝ(ˡ.ͤ4Ш;ӬAְEسHڵJܸJ޹H޺F߻C@>>?;彘2۳ 8HE#CNAy@>=>?BDbG6L]UB%1 )H%!Ę ɝ#͢+ѧ6ԬAرHܵN޹QSSRQRTTNæEGX`PKN[zl)=/<)Ø$Ȝ"͡'Ҩ4خCݵOW]acff`VōL߻O9@23,ƛ(̡'ҧ.خ<޵JV]^\WĶQMDسJ޹?K7Ě&1ˠl-Х-ժ2ٯ:ܴ@޷D޸Dܶ>=ԯ L%u[C4ɠ/̣.̤+Ɲ~V???? ??( ^^^^!U}ꆎh7yes.}wsrty~ᶈz]uPpqpy{rlhecbbfoyzٳv:lt R€unjgdb_]ZXY`l{twPfxtI{rmjhfca^[WSQPU`}gx>a|sp$g~|zywvuutttttsttuutrnjea_][XUQNHC>9525;;9cǓ}{ywvtsrqqppoooonnooooomjea^\ZWTQMHC>95238^7B}{xwusrqpponmmllkkkjjjjkkkjhfb_\ZWUROJE?:5227|2B芲|ywusrqponmmlkjjiiihhggggggfffeca^[YWURNIC=8427ÛK}?4{xvtsrpponmlkkjiihhggfeeedddddcccb`_][~Y~X~VTPKD>858?|=~Rׅ{xutrqpponmllkjiihggffeeddccbbaaaa```_^]~\}Z|Y{Y{W|U}P~IB<<}Ay)?zi遹{wusrqpponnmllkjiihggffeedccbbaa``__^^^]]\\~\|[{[y[y\x[xXyRzK{FzHwcG{Il w}xusqqppoonnnmlkkjihhgffeddccbba``_^^]]\\\[[[[~[|\z\x^w_u`u`u]uXuUsVpVqz~yusqppooooonnmmlkjjihggfeeddcbba``_^]]\[[ZYYYYYYZ}[{\x^v`tbsdrerdpemgGii~}r{vspoooooooooonmmllkjihggffedccba``_^]\\[ZYXWWVVVVVW~X|Yy[w]t`rdqgojmliBemoiyzybwspnnnnnnoooooonnmmkkjiiggfeedcbba`__^]\[ZYXWVUUTTSSSTU}VzXwZu]rapfnikkh/ilm`uvvJtqnllllmnnooooooonmmlkjihhgfeedcbba`_^]\[ZYXWVUTSRQQQQQQR~S{TyVvZs^pcngjhghh~qs.qnljjjklmnoooppooonmllkjihgffeddcba``_^]\ZYXWVUTSRQPOONNNOOO}QzSwVt[q`nckehdiopolihhhjklmnooppooonnmlkjiihgffedccba`__]\[ZYWVUTSQPONNMLLKKLLM~N|PxSuXr]n`kydgaimoljgfffhiklmnoopooonnmlkkjihggfeedcbba`_^]\[ZXWUTSQPONMLKJJIHHHIIK}MzPwUsYo[lJZp\hobiMgedcdfhjklmnooooonnmllkjihggfeedccbaa`_^][ZYWVUSRPONMKJIHGGFEEEFFHJ{NxRtVoWmWmgh ecabbdehjklnnoooonnmlkkjihggfeedccbba`_^]\[ZXVUTRQONLKJIGFFEDCCBBBCEG}KyPtRpVmToegda__`bdfhjkmmnnnnnmmlkjjihgffeddccbba``_^]\ZYWVTSQPNMKJHGFEDCBAA@@@@ABE~IzMtQq>Osafj_bG`^]^`bdfhjklmnnnmmllkjihgffeedccbbba``_^]\[ZXWUSRPOMKJHGEDCBA@??>>==>>@CGzKuOp Mraa_\[\^`begijllmmmmllkjiihgfeedccbbaaa``_^]\[ZYWVTRQONLJHGEDCA@?>>=<;;;;;<>BFyIuLHwNo`"]l[YY[^`cegijkllmllkkjihgfeddcbbbaa```__^]\[ZYXVUSRPNLKIGEDCA@?>=<;:998889:=AEyJt Hv\]%ZWWY[^acfhijkkllkkjiihgfedcbbaa```___^^]\\[YXWUTRQOMKIGFDCA@>=<;:9887676679=;:9877654444468=B~KxE{YY1VSTVY\_bdfhijjjjjihgfedcbba``__^^^]]]]\\[ZZYWVUSRPNMJIGECA@>=;:987654332212359>C}&A~Y\VSRSVZ]`befhiijjiihgfedcba`__^]]]]\\\\[[[ZYXWVUTRPOMKIGECB@>=;:8865443211000125:@h;EwVW6SPQSW[^aceghiiiihggeedba`_^^]\\\[[[[[[ZZYYXWVUTRQONLJHFDB@?=;:8765432110/////027<:9765432100/...-..049@*>ST2QNOQUY]_bdegghhggfedcb`_^]\[[ZYYYYXXXXXXWWVVUTSRPOMKJHFDB@><;9865432100/..--,,-.16<`:TVaRNMORVZ]`bdefgggffedca`_^\[ZZYXXXWWWWWWWWVVUUTSRQONLKIFDCA?=;:875432100/..--,,+,-/3:VCRS'OLLOSW[^acdfffffeedba`_]\[ZYXWWVVVVVVVVVVUUTTSRQPNMKJHFDB@><:976543110/..-,,,+++,.28@>W,PuLJMPTX\_acdefffedcba`^]\[YXWVVUUUUTUUTTTTTTSSRQPONLJIGECA@><:87643210//.--,,+++++-07>(<QSMIJMQUY]`bceeeeedcba`^]\ZYXWVUTTTSSSSTTTTSSSSRQPONMKJHFDBA?=;986542210//.--,,+++++,/5=I;TMOWJHJNRVZ^`bcdeeedcba`^]\ZYWVUTSSRRRRRRRSSRRRRRQQPONLKIGFDB@>=;986542100/..--,,+++++,.3;h9RV LGGKOSX[^`bdddddcba`^][ZYWVUTSRRQQQQQQQRQRRQQQPPONMLKIHFDBA>=;986542100/..--,,+++++,.3:ŀ6?NO2IEGKPTY\_abcdddcba`_]\ZYWVTSRQQPPPPPPPQQQQQQQPPPONMLKIGFDBA?=;986542110/..--,,+++++,.2:ȓnxDU7LwFEHLQUZ]_abcdccba`_]\ZYWUTSRQPPOOOOOOPPPPPPPPPPOONMLKIHFECA?><:87543210//.---,,++++,.2:ˠODNQIDDHMRVZ]`abcccba`_^\ZYWUTSRPOONNNNNNNOOOOPPPPPPOONMLKJHGECA@><:976432100/..--,,,+++,.2:ͩK CJL:FBDINSW[^`abccbb`_^\[YWUTRQPONNMMMMMMMNNOOOOPPPOOONMMKJIGFDBA?=;:87543210//.---,,,,,,.2;ϭK DQ>JxDBEJOTX[^`abbbaa`^][ZXVTRQPONMLLLLLLLMMNNNOOOPPPOOONMLKJHGECA@><:976532110//.---,,,,-.2<ѫM ELP GABFKPUY\^`abbaa`_]\ZXVTSQPNMLLKKKKKKLLMMNNOOOPPPPOOONMLKIHFECA?=<:876432100/.---,,,,-.3<ӣQGJK)D@BGLQUY\_`aaaa`_^\ZXVUSQPNMLKKJJJJJKKLLMNNOOPPPPPPPOONMLJIGFDB@?=;:87543210//.---,,--/4>Ә^HGIYB?BGMRVZ\^``a``_^\[YWUSQPNMLKJIIIIIJJKKLMMNOOPPPQQQQPPONMLJIGEDB@><;986542210/..------/5>Ԉ LLF@?CHNRVZ]^_```_^][YWUTRPNMKJIIHHHHHIJJKLLMNOPPQQQRQQQQPONMLJIGECA?><:976532100/..----.07As??JN D>?CINSVZ]^_``_^]\ZXVTRPOMKJIHGGGGGGHIJKKLNNOPQQRRRSRRRQPPNMLJHFDCA?=;:87543210//..---.19C[AGIB=?CIOSWZ]^___^]\[YWUSQOMKJIGFFFFFFGHHIJKMNOPPQRSSSTSSSRRQPNMKJHFDB@?=;986532100/...--/2;F=CFH;@<:87643210//..../3=H EDF\><:97643210//../18BݕmNLB<;?EJOSVY[\\\\[ZXVSQOMKIGEDCBBBBBCDEFHIKLNOQRSTUVWXXXXXXWVUTRQOMKIGECA?=;986542100///02:FhCHKUA;:?DJOSVYZ[\[[ZXWURPNLJHFDCBAAAABBCEFHJKMNPQSTUVWXYYZZZYXWVUSRPNLJHFDB@><:976432100//04>I8FGK @::>DINRUXZ[[ZZYWUSQOMJHFDCA@@@@@ABCEFHJLNOQSTUWXYZZ[[[[ZZYWVTSQOMKIGECA?=;9865321000016BMJFI ?99>CHMQUWYZZZYXVTRPNKIGECA@????@@BCEFHJLNPRTUWXYZ[\\\\\[[ZYWVTRPOLJHECA@><:87543210003:EnPEI>88=BHMPSVXYYYXVUSQOLJHECA@?>>>>?@ACDFIKMOQSUWXZ[\]]^^]]\\[ZXWUSQOMKIFDB@><:97643211014=HWE%zDH>88===>>@ACEGILNPRTVXZ[]^^____^^]\[YXVTRPNLIGECA?=;98654221126BM"JEI =77:@EJNQTUVVVUTRPNLJGEBA?>=<<<=>?ACEHJMOQSVXZ\]^_`aaa``_^]\ZXVURPOLJGECA?=;:875432124:E[PDJ =769>CHMPRTUUTTRPOMJHFCA?><<;;<<>?ACFHKNPRUWZ\]_`abbccbaa`^][YWUSQOMKHFDB@><:876432226>IZE[ES=658=BFJNPRSSSRQOMKIFDB@>=;:::;<>?ACFILOQTVY[]_abccddddcba`^\ZXVTQOMKIFDB@><:976543248CNKD1=647;@DILNPQRQPOMLJGECA?=;:99::;=?ADFJMPRUX[]_abdeefffeedba_^[YWTRPNKIGDBA?=;98654345k7359>BFILNOPOOMLJHFCA?=;:9999:;=?ADGJNPSWZ\_abdfgghhhgfedb`_]ZXUSQNLJGECA?=;98754458AKEI=?J7237;@CGILMMMMLJHFDB@><:988889;=@BEHKOQUX[^`bdfhiijjjihgedb`^\YVTQOMJGECA?=;:875446;FRN>@'82259=ADGIJKKJIHFDB@><:9877789;=?BEHLOSVY\_bdfhijkllkkjhgeca_]ZWUROMKHECA?=;:876558@Jh>U@C92036:>ADFGHHHGFDBA?=;98766679:=?BEIMPSW[^`cehjklmmmmlkjhfdb`^[XUSPNKHFCA?=;:87656:EP!MEg;30148;>ACDEEEDDBA?=;987655568:<:87668?I|S};=[500258;>@ABBBBA@?=;:8765445679<:8777;DN1L=?%70/0258;=??@@??>=;:87644334579;?BFJNQUZ]adfilnopqqqqponljhec`^[XURPMJHEB@?<:8879?I]Q?E9ߦ1./0368:;====<;:9875433223468;>AEJNQVZ^adgjmnpqrrrrqpomkifda_\YVSPNKHECA>=:988AEIMQVZ^aehkmoqrssssrqpnljgeb_]ZWSQNLIFCA?=;99:@IXQ=?7/--/0246778887665332111123579=@DHMQUY]aehknpqrstttsrpomkhec`][WTQOLIFDA?=;::=EO=MA\:ۈ2-,-.013455555433211000012469<@CGLPTY]adgkmpqrttttsrqonkifda^[XUROLIGDA@=;;<=>DM`S:<>4.+*++,-.///////....-../01369<@CHLPTY]adgkmoqrssssrqpomjgeb_\YVSPMKGEB@?>AIR%P>D8؛1+***+,,--......------./1257:>BEJNSW[_bfiknpqrrsrrqpnmjgeb_]ZVSPMKHEB@?@FNeU:<74-*)))*+,,--------,---./01369<@CHLPTX]`cgjlnppqqqqponljgeb`]ZVSPNKHDBAAEMUR=F9ֆ1+)(()**++,,-,-,,,,---./0247:>AEINRVZ^adgjlnopppponmkjgeb_]ZVSQNKHDBBDJR8Nj;="6.)''(()*++,,,,,,,,,--./01368;?BGKOSW[^adgjkmnooonmlkigdb_]ZVSPMJGDCDIOqbS>/:U3*'&'(()*++,,,,,,,----./0247:=@DHLPTW[^bdgiklmmmmlkjhfda_\YVSPMJGEDHNUS?F8׊/(%&''()*+,,--------../01358;>AEIMPTX[^adfhijklkkjhgec`^[XUROLIGFGMS*Q>>>>;>5ڲ+%$%'((*+,-------....//12469;?BFIMQTX[^`cefhiiiihgfdb`]ZXTQNLIGHMSKGVy{o j!kFjgi}hgdbwb\b8cwpI6<,2(#$&'()+,-.../....///0023579<@CFJMQTW[]`bceffggfedb`^\YVSPMJIHLRj^Vbe_&]l\\ZYWTQLHB?>=?UCoX?&8>/%#%&')+-..///000//000113468:=@CGJMQTWZ\^`bcddddcb`_]ZWTQOLJJLQZVQMuS)TSRQQPPOMJE>7/'##$&*0136F,##%'(*-/0001111000011224579;>ADGJNQTVY[]_`aabaa`^]ZXURPMKJMQWUIH LhMMLLKKKKKKJHD>7.¢%âĢġàŸ<4D)#$&(*.02222222222222233568:ä9Ť1ƥ(Ǥ ȤȤȣǢšĠŸC 35'"%')-134444433333333345679;=?BEHKMPRUWXZ[\\\[ZYWUSQNLKNRVT;wJ?,CED@<:::;<=?@BB£CäBĥAť>Ʀ:ǧ3ɧ+ʧ"˧˦˦˥ʤȣǢŠK(4%"%(+1577766655544444456789;>@BEHJMOQSUVXXYXXWVTRPNLLNR~Y V9zC=0AC?:64345679;=¡>ã?Ĥ@ťAƦ@ǧ?Ȩ=ɨ:ʨ4˩.̩&ͩΩΩϨΧΦ̥ʣȭj ْ"&).59::99887666555556789:<>@CEGJLNPRSTTUUTSRPOMLMOSaZV/n?9*?@;6200002356 8á:Ģ<Ť=ƥ>Ǧ?ȧ?ɨ?ʩ>˩<̪9ͪ5Ϋ0ϫ*Ь#ѫѫҫҪҩѩЧϨ%*29<<<<;:9987766666788:;=?ACEGIKMNOPQQQPPNMLLMPT=eX97=>81.-,-./01ž3ß5ġ7Ţ8Ƥ:ǥ<Ȧ=ɨ>ʩ?˩?̪>ͫ>Ϋ<Ϭ9Ϭ5Э1ѭ,ҭ'Ӯ"ԭխխ֭֬֬ԫӲ",6<>>>>==;:98877777889:;=?@BDFHIKLMMMMMLKKLNQV=]63~ ;;4.+))*+,-/Þ0ğ2Š4Ƣ6ǣ8ȥ9ɦ;ʧ=˩>̪?ͫ?ά?Ϭ?Э>ѭ<Ѯ9Ү6ӯ3ԯ/ԯ+հ'ְ"װذٰٱڱڰٱپ*8?@@???>=<;:988778899:<=?@BCDFGHIIIIIIJLOREXU5}J߮8a91*'''()*+œ-Ý.ğ/Ơ1ǡ3ȣ5ɤ7ʦ9˧;̩=ͪ>Ϋ?Ϭ@Э@Ѯ@ү?ӯ>Ӱ<԰:հ7ձ5ֱ2ױ.ײ*ز&ٲ#ڳ۴ܴݵ޵޵޸$6?@@@@??>=;:98888889:;<=>?ABCDDEFFFHJMQ`U:]3360'##$&'()Û+Ĝ,Ş.Ɵ/ǡ0Ȣ2ɤ5˥7̧9ͨ;Ϊ=ϫ?Ь@ѮAҮAӯA԰AԱ@ղ?ֲ=ײ<ײ9س7س4ٳ1ڴ.۴+ܵ(ݶ%޷"߷ 0@BAA@@?>=<;98888889::;<=>?@ABCDFILOaTcX++"(" "$&˜'Ú)Ĝ*ŝ+ǟ-Ƞ/ɢ0ʣ2ˤ4̦7ͨ9ϩ<Ы>Ѭ@ҮAӯB԰CձCֲC׳C׳Bش@ش?ٵ=ٴ;ڵ9۵7۵4ܶ2ݷ/޷-߸*(&$$##$>DECBA@@?>=<:98888889::;<=>@ACFIKOHRnZ!!|Z!#Ø%Ě'Ŝ(Ǟ*ȟ,ɡ.ʢ0ˣ1̥4ͧ7Ϩ9Ъ<Ѭ?ӮAԯBհDֱD׳EشEٴEٵEڶC۶B۷A۶@ܷ>ܷ<ݸ:޸8߹6420..--,,g.MGFNDCBA?>=<:9988889:;<>?BDGILXOVS-4)&g"—!ę#ƛ%ǝ'ɟ)ʠ,ˢ.̤1Φ3ϧ7Щ:ѫ=ӭ@ԯBְDױE׳GٴHڵHڶH۷HܸHݸGݹEݹD޹B޹Aߺ?><:9888997Ṡ7ر6ܴLNHF=DpCBA@?==<<<==>@CDFIlKX3+X&!×ř Ǜ"Ȟ%ʠ(ˡ+ͣ.Υ1Ч5ѩ9ӫ=ԭ@ְCױEسGڴI۶JܷKݸL޹L޺L߻K߻JIHFEDCBBBDFFB佼?ܶ!@߸`LGF$E?EXDlCzCBCD{EmGYI?J#MUAJ690J+%–řǛɞ!ˠ%̢(Τ-Ц1ҩ7ԫ;ծ@װCٲFڴI۶KݷM޹OߺOPPPONNMLLKLMPSQJEݸ.FFЫl+%; 3=.("ĘǛɝˠ!͢%ϥ+ѧ0Ӫ7խ=ذCڲGܵKݷN߹PRSUUVUUUUUVWY\]XOHݹ2PEԱ:>621+—%Śɝ˟΢"Х'Ҩ/ի6׮>ڱDܴJ޸NRUXZ[]]^_`bdfgd[PIܷ(VEѬ=C6)2-Ø'Ǜ!˟΢Х$ӧ+ի4خ<۲D޶KQV[^acfgikmmjbWN⽅H׵KC?I8#4u0Ś*ɝ$͡!Ф"ӧ'ի/خ8۲@߶IPW\adgijhe_VO⾶IݸHD԰Gٴ?L:6˜i2Ɯ-ʠ(ϣ$ӧ%ժ)ح0۱8޵@HNSWYXVRNJߺGڶYBӭn:ɣCM=8ÙO5Ǟ1̡-Х*Ө*֫,ٮ0ܲ5޴9=@BB߸BݶB۴Aײ@>Э!zf:á=>;Ś#8ȟW5ˢ1Τ/Ѧ.Ҩ/Ӫ1Ԭ3լ6խ8ԭi:Ҭ9;Ъ=Ü<ȡIT; 5Ǟ2ɠ2ʢ3ɣ7Ơ =;Ù?????????|??PNG  IHDR\rfirIDATxy%IU&ZnVDDlV7AtDW:n(˰ (R=, "3,BwW͌#32O8W婺/3#"#ΉL`I&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&dI&9ӄJ~ҵk w>ܖ|s+O9t-1} p 🂣~費_yxcINFߺK tUs&0;?Lj.Gv$3+]}7`UQ7Y .@p8r "nu8`B4 dhM7~etE>$M>=TUZ흫|ӂw趑)vSiMẖXlas6/ވ_=3VVjqgpѢ uwZK[$OFd?rzz:KB~TRHw]!Bc槶#r?~#g:nvh~үoƹ>>-ZߥA>`AS[ڟV@G) FOvm&?~|kK-o03F< OrUcl5A4}݁ < DK<=N:Ꞃ6Zz>(ucz% %3@z Ǐo67~Ϸ{L&˞+߾^@F j6QGp>Uh+߃]Xl1G/U B!dleslnׅ~7LrÖ=SzEpO:_n<@t?zCSHIi!zOBF2]F.Aw>M۞Whv+}/^~Cozl<յ-fk[{^b@_{sH,Đ&hB$AWk xsOsdiQGu{<~Kn뻗^} = >mJ%a(7(=#H܂NAАGPgK*yɗ^-A芣;ޜz-Y|/4%T9zჇj'ߺsۭo q`Ĩ~?Fb)dN܉H~%"O;``~]{0L;fPck=[}Y%wh͠p z7>L5@LԾ\u@S?9<i%D<{Ӝ߼%95ݸ|>K(A\J<2(ۭ['u$I@ +@d ]De00^$``(7k6gw~d^rMpW!ܓ<Do]8]?͚ݮ+VEԷ$ܽ3>r@s1#TIˮ*#G~Q5s:9^ @UOXt_d_< 1Y$#RХ%_0JalmwiG^5\ ۽糽 p#eܼr [GsަНLTm%ЊnY7tZG[6Á7nIl9ip[Wi߰Yx鈾rKZ{=i\ 3}H݈)>hh+/rW`(-m{B`,tl~y8~i] "i! ZrB i{O7,sOl{$R?}9PgfU8+cmPiv{*ä5('#y3E/GT?Q 6[%Ad(k M`̷t1,U9<{~ 7jA_,|w-$M.0hJGG@~AT!4j^8+| qo+p}9i$t-0H,uD1y+`)dQܞ$ ^<(:4}xh!,÷~*wY=wѭp5kKŘ, ZwqbzT5B !84YTW_ 继RG^v>7[wۉJa ?97g _?AZw0dRī. z0GKetni_0.nt윯4PMgybT:^} I'zzJ@ޏ"б.MS-3h&Tzï[|kw`ct9ppij#OL_Yz~Wtap%)mV_ƒ=Ke&a`Q9H klkE J߾e`ŇM% mՐIiGX% R`nICh<l̞{hb?d~V?;Ckk7&蔀@O4mGY Bץ>eVIZk$lB\sTt*nATh7`/+[t>֟,R,z shZE/.qх/d ]NZ/ꮏY?̡esuTԔR%VLcr^`dv_!"dIhZS. Ⱥı!&|<:;O؃:'q#EnP27V_ @ 1ur@ZeǼ}nQ/޺to+ >tg @Rv@{RB8a;Gp%pnob΄[ncf:5٭6C]Spqρ9@hoz]AGT_햻2}dh/~3p0o\;ޭuf-.k *?^q(A=uJYX%DYlr 1 _B-o[P/5g_x=Ujx0ȑo A;_ )ՐQtt]O@.[&wV?pC}mΩo=3TÓwUoonH?ȳtTH$xNsyy3R0@ Al{O8_>#4 DY 1 TE lPszeb"[Qc0-,5zisM6sgVyT.w3y[UgKǷK%0B3(+L;$?` 4<ze7QOd9e >O8TC ]`0N["J `X>I.*FX_-ja 7s_Kg[_sfGwڏb'Ynt-%䋍h>H$2F!NՍ?nG@% p0Wܞ_zΝ_S3QN_|w؁5\WHSoCwnA BOTƘˀ_1L] 3Z ȯ]ppp{ʇhy[[9Tk8*飠GF_6K@Kq GRA(Dv/ϛчڷ*p9 ?]?P|El_}ip;A2`P1ljOv[` ,/(?38qom؋?.mUyT>R~:8@-PN^##a耟 H:i0X/jŶu׼TcLӢ/7ѫ9[e9u7 D&.AbYzHHkA@Tw]zH>fůƽ1kȻ*Y}w;Xg%6ec>Y^ۘUh);_W'SHp1Ztǘ],X+@'<饵Hנv?`jP},@*@ߍ@10^kq-}^iW uz8=gsE ݁4m%1ׂŏ#H>,>pC`n|]荍9 '2,?Dߥ`*_>"CX哱8FpvQ! qlSh<0 \DHw@Ch[Zj7((G^o\WwUuwvلP$Fb$t?>K sX?_}W衳}Vg- $V_0FWmBҘl{2ȮCqыP=I~L @0 tJ`kڻ]yS{9u_ g|մ/th?긏g11}#I=Տ~>p4?4;yt_κ׷1T oHFx:*´/(+ؗvXkﰔ# __6R @3  @ ڛvp}3AZŭ.| -s]q"է8'Hrȑ߿񾿼q8*_NZuH$W֝Y~=5w̒kd +H{m H Э7AnsWDϙ"'` 3g-ڡ6oj}?tl[&c/=ppf*~ An\.KV2)򗀯ն9>`?{#.R_(9H(h` 4P{l6ٞo_f:rW[{w8t|c}}BK,>?IPgJ#zdЕ,EP Ym1 PJ, .u2%$@17k>{.;*|M_G766fU|ԁ_Z}ĦK+YY_;a.^H6<ۤLNbU!ޢ1עa6}j~]vbaeR+ѣ:4x`cլAUu~?V7tE Q^0W+ }E.aP^bEݱ2*^m+7w!L `Ef5fUu& A?>Q_|:G*NS27*d]Cr=Fi Ojjc_yeS:#dR+{ammj綻Ocx3Qk0ƕȘ"謸,\R |I^rmf %E`N%f◤}5:~㺎dl@~{Zw<p=I,սj|6?ŅN&H-1Zt?2,φ~Jaճ2گ|{ɧÒmq-34U@Zq#Cҗ#kko~?+˒<+HV V4[H& B$J Jm05fn~/Ϟ .ZUڬFC]t]+M _ʕ`P#¶"飱iIAh[1@l/x,_vZp jȻpV&P#GyZUw(L6بFodZ,G h+ȫ%\, eJ1 Q@3p ֪Ŭ>qXiL ~`߻f%z=ߤE,l[Uۊd]Xa\oF +Ӭ{YRh|p#Pk~~s6<2)C}h׿2uw /Az>stgצ ,r f3/A`ڛ/ Ҁ.Rrol#>3:G *wo.0f7?}Zܲme?e<W%pPz]V@b,f]! ƍ5Ye5y(3,WĚbPZ{u{F?o0/^G/;}ڧS&GxӵjŁ>$/gJ`Ҏh.A%13%_6}>{{>FPm>U&?+B+}/|mx`ƱZsZFw=ݥeoב(֥("W*xK`TEG `غN}"_ާlIҼ{.oeSW`V׫/=gL @'WUdl2V @&*/Rd[Ӣ Ar*mn8R7)ӡ7:k]tFn=P5>]G9#>nyIjkrE&R4]sD}K@<4/X Ȭ36Ҳ}Wan-ZgGw{;/P}ki@'s󝯟0PN|8Źe#髈U^Sft] Ծ b?h#'Sl4S1,zirA@}'=s\w *7w~V69r;CkU}PNt!eW~◘@ Y=0"OoYseV*YtR$1Oʘ"KOjG!% }7tĨaãqɤ||v`#x@n 2+Jl2BҘR-h%ĹikT9]IaegCypeG XAT~Zg+^iIf~qh#tkzX iu`I-[w eM6 k'#̉2!_fWD-Drxp?g{G/?:fﯨ߀3'0b)gHڧg JH<=r\ў`Ȫ E>H%X~/zJe+҃oV`N] w]{XoQX7܇$]mtSsI/9ŗq[,wa'vxd @G5~aLn~@n [ ,ܤڟG ^闛@Y7CTPC5%x}s[<e"p +O}st[y< N~T_Kw`wޯCG9e?Z > 2/z [[Y|7>*ߧ]0)Y< kMN x`̶q"mve_+ pk/g S~ ^t-J+z}Xj J(Yjw޳g.0]dn@ \x_-}U ,r'~M{?Dx*fPyפ~)YOt_H_ްXzk(3@M`ۺ>s}…<ɘFu \`UXUûȿH h@weFYmM3-ee^SWdhb >:Иmi'*A-wA쳲DEбbxj] 7Im-VpY%FdomE~)¯z P , J#e P{d><F(@,a^{(R0#k2?Yf ˾ߋ3N/uTg:@ DF[%qM%!QJY-㱗I#+)^^٩}>35i<?i'Kh+4܂b/д]:aQQipbf/1-2VX˕Gu~ 'ޚONX撊' lWYAX7"x)(_P}.s ҊvY:rPXܬxC'Qءh\5Xj%/Y}Bգk"\ [?ҥ(0"lG(~eig V߷mOyX.>|#~WoBz܁xf*үػw:-I@%dTX)ݗy(Y[_fu⢲uӓJ e/ i0['ɾRׯ_)p}&R Eo"ꗆȗu@Г,j:4)Z?Qi$)8Եk%}ޢX@'QYQ˒$DPvp1zO@\YRo WJ>/ >e55hqF"cibkQ t BG?@7ڬ}*.H>'HW!y[(}L>ޢ ӺNugE<CYꂃ1\ OOPp`[-:&POPdo֑iKۥbŽZ;й8w?cNA!nN?S?s/P|ؽHJ=OO9'$"שմWmXqF!kݢ2ܳJ̀瘴7 v*W?{qe0]u֟,Pjihy2X" Il4k]Bs)=GRy +ގ宁̤LJu pwsoC8Ųo_>$\oIKD!IWqJnI5RഽY Z?*S3V-/d7ĪS_gYB$yk4-# 0Q9#쓓]xӯ@Y%̡ocOr }%xd6Aї)Eגp2sesiV @v ?XUtӶ>l?dw?Ӂ@T8EEk1 9+@Q&q|]F|%-Y@dhg zW_[ʾQ$c'C?9 [P.L5񓒀{^f/1 %/ @ԭעQz^ .S/*ʌnPx.?ӄpM%b;kD JXFdb쬮U(2%ަ#fXt]^=+mvdWx',HC, AG}}`NJڼ>h ,9XM+ 1Km;r LR1{aUĸ~}8+؃Y<&+8Eo J%0V,YU,E5ִ]o*e_URCd2>:("awM@hP7ɾQD޹&-}+WՖ+3TL 2/֑g:N;@[`SQ0v(EדRcB`xe8Eo3 EǾ؛h%.?0 2(/A_10A9h/8* +E"rԾƽ4+P)b!'iq_o)}"A wR0`Mő bȾ+^֭٘d 6_6'_*ץR ]ga8@ D57 Bwm~$nF2.Y F_ xC+_[b,@g)k2<ԹpE`J%)[8$%bT`@ <ꇬTe(o!@1atoՀҭX>0LtBJI^Ж :c(\K0dֺL`jd?:zye? iG @"M뺤w+P'q,.Yld4)d^KhYJIקPO h1[X[ñ$Ϳb\R$2KbXd T:ҲrR" VN˘y%&`U 뜝 *#ʡtPT&B=@"ܺ?!$}w#)4,Х^r ;<Bz/Z+nVz]Ri׻ aĹsPyd**@[B1?=S Vj]bexm ŗ{fhsGoP&]˗PX^u8VKҬ}񭏆犲2Y{`Rxy_On nBZ+Tr#H!v O 7 WCZp/gN53v 8S:-,ۙT4=ZMpz}nZm-ʳUv2PpB%~>]}/0;1@II=đ"pK`Ibx^[[PSe Vnfgyl짎5ꛋ}=  z{bu/Do3E5hiCYٍK 4kVl`ח\OzGJ' +>?z8=y{Hd4ƅ1+nQ#3R(6z!iwpx7 oMqmqlxO cc}1}7~(,Ѱ0I7R.-+ \JF h e'9fO|mѳUe ˂PW O=%7 .~gL΁I%bZS]_pl&*`|r.K4{'- Jykj ;6##JJL pTSM=Ko@wg+(eT " l LZ>]:0Ӫ>qP!_Qe Xؠ"GMXY{{ZPB@@k W > .$!uP,tɺ-a+t j:.1Z݀%&*6DY>} nԗ)=ӓʪ bd#74CKO%+e=0@g׮=(ZpX\$V~1Y\f˺P%>zP%`( gwuPc !wܭA?p-P C>]QJVi\*W:WE"i%LxS}fc/iPtݠ%~i˥F{Uo+.9vA&0joN鍕x`PB }yηfOD'YIYR!Cv}Uֽ݉hK\6q<pPa5(|\꠴\-xv>}ν}21V$X0Ƣ+bdpg]o1lJV_3xoIYMdž+[Uh5d64 p/X.W}1)%k.Ylx$g@[i<`Y} :T-;.*~kf RET_^R`if ı,Ͳ+^/w8tK }O4o `y<` `%ؐ~/1tUohfE[B)Lv;f @Wԍ"-ޫ`}R+l}۴F>f.\!N1%X zL$nm6WnHf`e6:Yy]#/ȏ^f: a!|\}sq u6AwblR:@<9"p줘:Vi[}k, *?z^E@>dQ|Ɗ-1w+p߹[!rJ~B@S!oAK$)[:,@>7 ]F :s bEK.0: ()U:^ɏ:EZc?|ťpp PJIe+iP/wXR.KDn}Keӄs06m,0`H?.uu["#F>6L(;8~3Oz/jzNЕexYmdڂb@9^qF ƾb(-?pF} Wo\5=Ծ+X-%n]BߐT\c՗XRذWVu ')Gd&̋UYkqx$z9kua`oB8#) ^C}4 ղ1Yk2c]fneK(UC#,$&g\Ǫ@u󒺌܁UY@Sv׹ g8Iٿ {w/r!}ɦ(i`(d0me]|ye2˘1`~rȴnW&{n.[Xy'm-;N'm v1?*OY%Wȗ׏LwKN+䅎Kթo\*>Q_8i `;^# VēAJ,U5xgRBٕ|Vyb3}G;0n >}Hm) lq%p"4lg'\FB3} 5à Lj|)[+t1]+tZ4=BEaA2Zn*2˔Bf݋J6&NR@8d]ohTC-)T(=.IKX(~U/?NY(Vd#ƲsDqa `wIʾW0;]*@EUo?ZV^Xr~uuaYNU1 (36Cԥi}޷~RLU 58I s$_ X@lTI Jb)u @e\>*+2CN4#cPV=o> UaHYΛ$eR Ͼa4NQX,yGֺyˀԣ&+R84t^2߯^)ta(w2]|uu"8q蒊 >M yPz9'(Uzl]0L( }=Ku+r>zH0Fĸ]ʤ:i .@c5Gy) IYK%XD=$}O:בuώ%': g%WK<}%/7>q-YT?i躿z8I@'/zcH<\^!m)X.AU+7q=:WzL%䀖X "*։?G֕TЯ2-EC?Mpj7c$aKt7$QJ`2L}K..A r t@E D(ʺ{N2K#I0H/}CB4-X[͍w-L `%WGS= 쐹zr  AĂ2+--?},JLTgu PO*l,EЭy;iSREEz B1_:'UMh/;r|7V/lW/n<:!!ZYXV]g1 v4E\e~} @ o}2cwf >'tEP/f/?zR;˹>j*J)EoSpdbїvݗT?SLe @w#jg@@??CSx%CQ$@(৾K\&DOma6=)EpZ7 *#՗n=YF(pFȭ4/^K ?Y|@%#@+6!ݵ>oˈHbq5pg.׍Gt8+_ϡtpa@W d,ACCw& hePy,rV`8#U V ȕ>n<(o_W<) SA"&J'JS/c 1& !cȕ4R@g9}EuJ eM/\4x?ʩjϓ8_P]1_|Q(Tv|I# vK#"ew@+o h`iRbK@/u7mgL[6_ F|Q5w*4űin%D8ж2\\]zRAGXy|t&ONTV2(9J22RаQ:Y']1ջ6xX0S`0a0WJ/E/@O.|P&pBgA~G z,w u˷XҺjhTPQ%DK~d? Y|=V`hshܯv<)/,_G? GY)YPE2^R YЊvRNeqK(Jeҙ|= n@xo?J FA>/[%PP{u:mxR'!?~,Fm%^Rr;޲ XRnI[2;W;D .?s_#:kAF}oCg-sta:*& 7R<8cHԯˈ}7Y٬˭XL2I7X9%lGq,sKt/ҸKk6=C:Si< TZ//qIAFԳ #}}Gh*4uՎkxv_ȤvM{[%G ʁB5 [Z<򷁔)$>tWvLdպoev:oz,k%Q{>Pxlqw:[vIwT'4~_6B^kIR }_eÃ9(+nu?fzvM@(nTrRk.GP%/F${4EǼt:vQ軞3BƯ)+0-%.) mim]%-aل܆>D>)GJE?eδ#|]2%C&T=[_z> ]#.tۻU.CPl+:[JD$ˀX/2/1E1DtC-zF_E>0 cj"|_N )ÇÃq C @G@`YQ[Q>((BZ_n{5..;NA H)x#=axlG:W FH׳! 4?~|JiP=]aOme|S־/)Xo0'VeqЏâ/m-.]OU;"!ٹACw DmѮ~I,fZU`3Lq_!_@_$׻:ju~_Gc t֞9G*Je 3=Ϲzeޞ&E  m q `eEL04<`X_f2B|X+)`Osʫ2r$b^eBF߰}yp7Y{=c8BSx T")( +QntS b_V:7cKXy/g!Hp\/&>ЍǪOv P:TX4?i';)~3^6@d ,(tɸ},@Y,؀:16 -6Zp0+ɺ ?yG<|>J~og"8l7hpK.{&)pzp M~.>H%ac?d@i?\n)s7& e!aSooB< Qì>pݷ:v<f3{e??01*֟z|B $j[?[_'8'Ҩ>oOAG68}H:Ljvy{Z$XF՜ V+BNfP |c } ~vX[?/yZ$S_*n %6\tw^z{F20A 4e_0oX -?ڨ<8l7?Β`wakڞ/~r*ϐ[%,ˇԨH@>> h7^5OlsGiqYك9.=_g灮x7nsŶ'tp9|M=@+&܂bzcc,}5h~ne+ϙ;@9/d=?ZP3fVkxֱnk% 闞φhw QQ!UQovete| %oFGTͅ_OuIoo~ 6)A$JG*StAZp c$PH-! GIx0jlc ?a`2bgЃX4U;bp7j0H9B5mu?hDkN<>!,2e<\BY~EMB %`9BCX)箶>I7U 5m!2%#(XrYDQX@YaM%q>b?sp TAlc g{ݞVABS/E~͖3P ,8@nC!/EG ד} ue|NA%@G~F +La>u 8D柜U{ݖVIaBЬ}/Yk@TQ2/r/)geF&/tYTo /^oZ&޹3$#p/oð IJt~@]̄,ѧpICsM~kx 8?~/:F\rmnf'2M>Å D]\} ~C`7͸w1~Ib;HF S_7驰=i~ꏋHz'~g8>Zwi] ו~wO{>N=Mr}~˾ y.P߮ . œu њB0l֞٭4Qo/CRVq0Xy9JF]2nYF@ 3ت[E[u{ةL z.)? ah68.N`=#~YBGr6 `p*]¿G\,+bk[x9t9@Gx+uoj;L1eGoxc/TP}vb?:p9}W&]t> bV/uoC8}>cDh9@Ri]ij1. 8#hv!!N=wC&pzcKzipSFS"+ѥHL\2Co^) (v0|SUtB4u+ w/*~kki"2)eGA@ZE)~TrB!Hv$6 -:K~2bR tPx"{PREl q C ?1HYUwA@p7L&j~[._:;ߑ.8 av; A3ԁQ7vMvo]r{w*g/}7|#8A9Ь 2 :d+ FC cBפu#nA40)ׇ?Pyݲ M ];\;_}]g{"2Lq8#|í/'y8<oݒ#VY_$DRW~vG"(~8o\:O}P] rG06(7aif`ozaO`>_w݀?\[&D6c⋛=ytڿ=N$zϝ~Yw ֟Mhk8^)0 C%x\fq18h\& ;6ЧA-:?SooPF=\@\@.@K? &lo|;ۛDer&Ʉ~U+oRכws]Ƿqڱǂ@U`/F=^pC.vQs0u#x082ܾBQhsyԳ)|y[֎p+ kf ܔ:C >̀ZRh#:nJ9\] m{&@48IG0$HE4|@T[wH%7f.9_zˮdRܠ_ٱzz cl 4o<ƣi4n66{ Q&0 V?f @},nܸB ASzo|ztʤ&ۯ=9qt`ksvڗ%׻xL2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$L2$7l^ƭIENDB`lemonade-sdk-lemonade-d88f5d9/docs/flm_npu_linux.html000066400000000000000000000624311515430344400230020ustar00rootroot00000000000000 LLMs on Linux with FastFlowLM - Lemonade Server
Linux NPU Support

LLMs on Linux with FastFlowLM

This article will teach you how to run LLMs on your AMD XNDA 2 NPU on Linux using FastFlowLM. Get set up and then show us what you build!
Date:March 11, 2026
Authors:Lemonade and FastFlowLM contributors
Announcement

Updates for Linux Support

Today, it is possible to run LLMs and Whisper on the AMD XDNA 2 NPU. This solution is made up of:

  • Upstream NPU driver in the Linux 7.0+ kernel (with backports for 6.xx kernels).
  • AMD IRON compiler for XDNA NPUs.
  • FastFlowLM, a lightweight LLM runtime for AMD NPUs, which today has added Linux support.
  • Lemonade, ties everything together with a streamlined user experience.

Getting Started

FastFlowLM

FastFlowLM is a lightweight LLM runtime optimized for AMD NPUs. Today, FastFlowLM is adding support for Ubuntu, Arch, and other distros to enable fast, low-power LLMs on Ryzen™ AI PCs that run Linux.

This article will help you:

  • Understand Linux NPU support status and required platform versions
  • Install the FLM + driver stack for your distribution
  • Validate your setup with flm validate
  • Fix common firmware, driver, and memlock issues
HW Requirements

Supported processors

FastFlowLM on Linux requires an AMD XDNA 2 NPU.

Ryzen AI family Codename Status
Max 300-series Strix Halo Supported
300-series Kraken Point, Strix Point Supported
400-series Gorgon Point Supported
Z2 Extreme Handheld devices Supported

Note: Ryzen AI 7000/8000/200-series chips have XDNA 1, which is not supported.

SW Requirements

Runtime stack

This solution requires specific firmware, kernel version, driver, and runtime software to function. The quickstart guide below will help you install these requirements.

Item Requirement
NPU firmware Version 1.1.0.0 or later
Kernel + driver Kernel 7.0+ with amdxdna, or amdxdna-dkms
Runtime FastFlowLM installed
Memlock limit Must be high enough for NPU execution
Quickstart

Setup by distribution

Select your Linux distribution and follow the exact install path.

Ready to enable Linux NPU LLMs?

Install Lemonade, set up the dependencies described above, then start chatting and building!

lemonade-sdk-lemonade-d88f5d9/docs/gfx1151_linux.html000066400000000000000000000203411515430344400224300ustar00rootroot00000000000000 Kernel Update Required - Lemonade Server
Kernel Update Required
Your kernel has a critical bug affecting Strix Halo (gfx1151) systems

What's the problem?

Your system has a Strix Halo GPU (gfx1151), but your Linux kernel is missing a critical fix that exports CWSR (Context Wave Save/Restore) properties. Without this fix, ROCm may have incorrect VGPR counts, causing crashes for various workloads.

Technical details:

How to fix this

For Ubuntu Users Recommended

Install the OEM Kernel

Ubuntu provides an OEM kernel with the fix included. Open a terminal and run:

sudo apt update && sudo apt install linux-oem-24.04

After installation, reboot your system for the new kernel to take effect.

For Other Linux Distributions

Upgrade to a Kernel with the CWSR Fix

Upgrade your kernel to version 6.18.4 or later, or a kernel with the CWSR fix backported. The fix exports cwsr_size and ctl_stack_size properties to userspace.

1

Check Current Kernel Version

Open a terminal and run:

uname -r
2

Update Your Kernel

Use your distribution's kernel update process. For example:

  • Fedora/RHEL: sudo dnf update kernel
  • Arch Linux: sudo pacman -Syu linux
  • openSUSE: sudo zypper update kernel-default
3

Reboot

Reboot your system to load the new kernel:

sudo reboot
4

Verify

After rebooting, verify the CWSR properties are exported:

grep -E "cwsr_size|ctl_stack_size" /sys/class/kfd/kfd/topology/nodes/*/properties

You should see both cwsr_size and ctl_stack_size listed.

Need Help?

If you continue to experience issues after updating your kernel:

lemonade-sdk-lemonade-d88f5d9/docs/index.html000066400000000000000000001103631515430344400212300ustar00rootroot00000000000000 Lemonade: Local AI for Text, Images, and Speech

Refreshingly fast
on GPUs and NPUs

Open source. Private. Ready in minutes on any PC.

Chat
What can I do with 128 GB of unified RAM?
Load up models like gpt-oss-120b or Qwen-Coder-Next for advanced tool use.
What should I tune first?
You can use --no-mmap to speed up load times and increase context size to 64 or more.
Image Generation
A pitcher of lemonade in the style of a renaissance painting
Speech
Hello, I am your AI assistant. What can I do for you today?

Built by the local AI community for every PC.

Lemonade exists because local AI should be free, open, fast, and private.

Join the community

Built on the best inference engines

Works with great apps.

Lemonade is integrated in many apps and works out-of-box with hundreds more thanks to the OpenAI API standard.

Built for practical local AI workflows.

Everything from install to runtime is optimized for fast setup, broad compatibility, and local-first execution.

Native C++ Icon

Native C++ Backend

Lightweight service that is only 2MB.

Install Icon

One Minute Install

Simple installer that sets up the stack automatically.

OpenAI Icon

OpenAI API Compatible

Works with hundreds of apps out-of-box and integrates in minutes.

Auto-config Icon

Auto-configures for your hardware

Configures dependencies for your GPU and NPU.

Multi-engine Icon

Multi-engine compatibility

Works with llama.cpp, Ryzen AI SW, FastFlowLM, and more.

Multiple Models Icon

Multiple Models at Once

Run more than one model at the same time.

Cross-platform Icon

Cross-platform

A consistent experience across Windows, Linux, and macOS (beta).

Built-in app Icon

Built-in app

A GUI that lets you download, try, and switch models quickly.

One local service for every modality.

Point your app at Lemonade and get chat, vision, image gen, transcription, speech gen, and more with standard APIs.

POST /api/v1/chat/completions

Always improving.

Track the newest improvements and highlights from the Lemonade release stream.

Installation Setup
lemonade-sdk-lemonade-d88f5d9/docs/install_options.html000066400000000000000000000020541515430344400233370ustar00rootroot00000000000000 Lemonade Install Selector
🍋 Installation
lemonade-sdk-lemonade-d88f5d9/docs/marketplace.html000066400000000000000000000056001515430344400224060ustar00rootroot00000000000000 Local AI Marketplace - Lemonade

Local AI Marketplace

Apps from our partners and community that work great with Lemonade

Loading apps...

⚠️

We're Offline

Unable to load marketplace data. Please check your internet connection.

🔍

No apps found matching your search.

lemonade-sdk-lemonade-d88f5d9/docs/models.html000066400000000000000000000111011515430344400213720ustar00rootroot00000000000000 Lemonade Server Models

Models

Browse models and install them with the pull command. You can also register any Hugging Face model into your Lemonade Server with the advanced pull command options.

Loading models from latest tagged release...
lemonade-sdk-lemonade-d88f5d9/docs/news/000077500000000000000000000000001515430344400202035ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/docs/news/gpt-oss.html000066400000000000000000000355021515430344400224720ustar00rootroot00000000000000 Run OpenAI's gpt-oss locally with Lemonade - Lemonade Server
New Models

Run OpenAI's gpt-oss locally with Lemonade

We're excited to announce that Lemonade now supports OpenAI's gpt-oss models, bringing you the power to run these cutting-edge models locally on your own hardware! 🎉
Date:August 12, 2025
Authors:Daniel Holanda, Jeremy Fowers, Krishna Sivakumar, Victoria Godsoe
Overview

What is Lemonade?

Lemonade is a local AI runtime that makes it easy to run models like gpt-oss on your own hardware with privacy by default. It is optimized for fast setup, OpenAI API compatibility, and practical performance across common local acceleration stacks.

One Minute Install

Simple setup flow that gets the local stack running quickly.

OpenAI API

Works with many apps out-of-box and integrates in minutes.

Hardware Auto-Setup

Configures dependencies for your GPU and NPU acceleration stack.

Multi-Engine Support

Works with llama.cpp, Ryzen AI SW, FastFlowLM, and more.

Multi-Model Runtime

Run more than one model at the same time on a single machine.

Cross-platform

A consistent experience across Windows, Linux, and macOS.

Model lineup

Choose your gpt-oss model

Use 20B for faster local responsiveness, or 120B for deeper reasoning quality.

gpt-oss-20b Optimized

Optimized for lower latency and local use cases.

Total Parameters21B
Active Parameters3.6B

Perfect for everyday tasks and quick responses.

gpt-oss-120b Production

Production-ready model for high reasoning tasks.

Total Parameters117B
Active Parameters5.1B

Ideal for complex reasoning and advanced applications.

Advanced features

Both models feature OpenAI's sliding window attention and attention sink mechanisms, allowing them to handle long conversations and contexts efficiently while maintaining response quality.

Quickstart

Install and run gpt-oss

Set up Lemonade, download your model, and start chatting locally in minutes.

Install Lemonade

Use these quick download links to get started:

Operating System Downloads
Windows lemonade.msi
Ubuntu lemonade-server_latest_amd64.deb
macOS (beta) Lemonade-latest-Darwin.pkg

Other platforms? See Installation Options for Docker, Snap, Arch, Fedora, and Debian.

Run gpt-oss models

Pull and run the 20B model:

Run gpt-oss-20b
lemonade-server pull gpt-oss-20b-GGUF
lemonade-server run gpt-oss-20b-GGUF

For higher reasoning quality, pull and run 120B:

Run gpt-oss-120b
lemonade-server pull gpt-oss-120b-GGUF
lemonade-server run gpt-oss-120b-GGUF

Tip: keep models pre-downloaded to avoid startup delays:

Pre-download both
lemonade-server pull gpt-oss-20b-GGUF
lemonade-server pull gpt-oss-120b-GGUF
System

System requirements

Recommended memory guidance for each gpt-oss model.

gpt-oss-20b-GGUF

About 13GB RAM is recommended for optimal performance.

gpt-oss-120b-GGUF

Requires significantly more memory for optimal performance.

Ready to get started?

Install Lemonade and run gpt-oss locally with full privacy in just a few commands.

lemonade-sdk-lemonade-d88f5d9/docs/news/index.html000066400000000000000000000202371515430344400222040ustar00rootroot00000000000000 Lemonade News
Latest News & Updates
Discover insights, tutorials, and the latest developments from Lemonade
lemonade-sdk-lemonade-d88f5d9/docs/news/lemonade-arcade.html000066400000000000000000000207201515430344400240730ustar00rootroot00000000000000 Introducing Lemonade Arcade: AI-Powered Retro Game Creation
New App

Introducing Lemonade Arcade: AI-Powered Retro Game Creation

We're excited to announce Lemonade Arcade, a new application that transforms your GPU into a creative engine for generating retro-style games using AI! 🎉
Date:August 22, 2025
Author:Lemonade Team
Overview

What if your GPU could imagine games?

Meet Lemonade Arcade, a new application that transforms your GPU into a creative engine for retro-style games. Just enter a prompt with your vision and within minutes, a playable Python game pops open.

Lemonade Arcade Banner
Lemonade Arcade Demo
AI game generation

Input your idea, such as "asteroids with rainbow lasers," and get a playable game in minutes.

Game library and management

All creations are saved and ready to replay with source code and original prompts.

Lemonade integration

Lemonade Server is integrated to run the LLM locally with strong performance.

Model Stack

Powered by Qwen3-Coder-30B MoE LLM

Under the hood of Lemonade Arcade is a 30 billion parameter language model, served locally via Lemonade Server. This setup provides fast, responsive game generation when your hardware meets the requirements.

Chat-style interface Familiar

Simple chat interface for describing game ideas.

InputNatural-language prompts
OutputPlayable Python games

Just type what you want and watch it come to life.

Workflow

From prompt to playable

Lemonade Arcade combines the simplicity of a chat-style interface with the magic of a game emulator. Instead of emulating old games, it generates entirely new ones from your ideas. Whether you're a developer, hobbyist, or just curious about AI creativity, Lemonade Arcade makes game creation fast, fun, and endlessly remixable.

How it works

1. Describe your game

Enter a simple prompt describing the game you want to create.

2. AI generation

The 30B parameter model generates Python game code.

3. Play instantly

Your game opens and is ready to play within minutes.

Example prompt

Space Invaders Twist
"space invaders with exploding bullets and rainbow colors"
Pong in 2D
"Pong, but the paddles can move in 2D"
Flappy Bird Coins
"Flappy bird with coins for me to collect"
Space Invaders X

Ready to create games with AI?

Transform your ideas into playable games with the power of AI and Lemonade Server.

lemonade-sdk-lemonade-d88f5d9/docs/publish_website_docs.py000066400000000000000000000070531515430344400240060ustar00rootroot00000000000000# From lemonade repo root, run the following: # pip install -r docs/assets/mkdocs_requirements.txt # Then run this script to publish the documentation to docs/docs/ # python docs/publish_website_docs.py # Standard library imports for file, directory, regex, system, and subprocess operations import os import platform import shutil import re import sys import subprocess def _get_venv_executable(name): """Get an executable path from the venv based on the current Python interpreter.""" python_dir = os.path.dirname(sys.executable) if platform.system() == "Windows": return os.path.join(python_dir, f"{name}.exe") else: return os.path.join(python_dir, name) def main(): # Print the current working directory for debugging print("[INFO] Current working directory:", os.getcwd()) # Define source and destination file paths src = "docs/server/README.md" dst = "docs/index.md" # Check if the source README exists; exit with error if not if not os.path.exists(src): print("[ERROR] docs/server/README.md not found!") sys.exit(1) # Read the source README, making necessary replacements with open(src, "r", encoding="utf-8") as f: readme_content = f.read() # Write the content to the destination index.md with open(dst, "w", encoding="utf-8") as f: f.write(readme_content) print("[INFO] Copied docs/server/README.md to docs/index.md.") # Read the just-written index.md and perform additional link fixes for website publishing print("[INFO] Fixing links in docs/index.md...") with open(dst, "r", encoding="utf-8") as f: content = f.read() # List of (pattern, replacement) tuples for fixing internal documentation links replacements = [ (r"\(\./apps/README\.md\)", r"(./server/apps/README.md)"), (r"\(\./concepts\.md\)", r"(./server/concepts.md)"), (r"\(\./lemonade-server-cli\.md\)", r"(./server/lemonade-server-cli.md)"), (r"\(\./server_models\.md\)", r"(https://lemonade-server.ai/models.html)"), (r"\(\./server_spec\.md\)", r"(./server/server_spec.md)"), (r"\(\./server_integration\.md\)", r"(./server/server_integration.md)"), ] for pattern, repl in replacements: content = re.sub(pattern, repl, content) # Write the fully processed content back to index.md with open(dst, "w", encoding="utf-8") as f: f.write(content) # Remove existing docs/docs if it exists if os.path.exists("docs/docs"): print("Removing ", os.path.abspath("docs/docs")) shutil.rmtree("docs/docs") # Build the documentation using mkdocs print("[INFO] Building documentation with mkdocs...") mkdocs_exe = _get_venv_executable("mkdocs") subprocess.run([mkdocs_exe, "build", "--clean"], check=True) # Move the generated site/ directory to docs/docs/, replacing it if it already exists print("[INFO] Moving site/ to docs/docs/...") # Check what mkdocs actually generated if os.path.exists(os.path.abspath("site/docs")): # If mkdocs generated site/docs/, move that content source_dir = os.path.abspath("site/docs") elif os.path.exists(os.path.abspath("site")): # If mkdocs generated site/, move that content source_dir = os.path.abspath("site") else: print("[ERROR] No site directory found after mkdocs build!") sys.exit(1) # Move the correct source directory shutil.move(source_dir, "docs/docs") print(f"[INFO] Moved {os.path.abspath(source_dir)} to docs/docs/") if __name__ == "__main__": main() lemonade-sdk-lemonade-d88f5d9/docs/self_hosted_runners.md000066400000000000000000000272411515430344400236320ustar00rootroot00000000000000# 🌩️ Self Hosted Runners 🌩️ Documentation This page documents how to set up and maintain self-hosted runners for lemonade-sdk. Topics: - [What are Self-Hosted Runners?](#what-are-self-hosted-runners) - [NPU Runner Setup](#npu-runner-setup) - [Maintenance and Troubleshooting](#maintenance-and-troubleshooting) - [Check your runner's status](#check-your-runners-status) - [Actions are failing unexpectedly](#actions-are-failing-unexpectedly) - [Take a laptop offline](#take-a-laptop-offline) - [Creating Workflows](#creating-workflows) - [Capabilities and Limitations](#capabilities-and-limitations) ## What are Self-Hosted Runners? A "runner" is a computer that has installed GitHub's runner software, which runs a service that makes the laptop available to run GitHub Actions. In turn, Actions are defined by Workflows, which specify when the Action should run (manual trigger, CI, CD, etc.) and what the Action does (run tests, build packages, run an experiment, etc.). You can read about all this here: [GitHub: About self-hosted runners](https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/about-self-hosted-runners). ## NPU Runner Setup This guide will help you set up a Ryzen AI laptop as a GitHub self-hosted runner. This will make the laptop available for on-demand and CI jobs that require NPU resources. ### New Machine Setup - Install the following software: - The latest RyzenAI driver ONLY (do not install RyzenAI Software), which is [available here](https://ryzenai.docs.amd.com/en/latest/inst.html#install-npu-drivers) - [VS Code](https://code.visualstudio.com/Download) - [git](https://git-scm.com/downloads/win) - If your laptop has an Nvidia GPU, you must disable it in device manager - Open a PowerShell script in admin mode, and run `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned` - Go into Windows settings: - Go to system, power & battery, screen sleep & hibernate timeouts, and make it so the laptop never sleeps while plugged in. If you don't do this it can fall asleep during jobs. - Search "Change the date and time", and then click "sync" under "additional settings." ### Runner Configuration These steps will place your machine in the `stx-test` pool, which is where we put machines while we are setting them up. In the next section we will finalize setup and then move the runner into the production pool. 1. IMPORTANT: before doing step 2, read this: - Use a powershell administrator mode terminal - Enable permissions by running `Set-ExecutionPolicy RemoteSigned` - When running `./config.cmd` in step 2, make the following choices: - Name of the runner group = `stx` - For the runner name, call it `NAME-stx-NUMBER`, where NAME is your alias and NUMBER would tell you this is the Nth STX machine you've added. - Apply the label `stx-test` as well as a label with your name to indicate that you are maintaining the runner. - Accept the default for the work folder - You want the runner to function as a service (respond Y) - User account to use for the service = `NT AUTHORITY\SYSTEM` (not the default of `NT AUTHORITY\NETWORK SERVICE`) 1. Follow the instructions here for Windows|Ubuntu, minding what we said in step 1: https://github.com/organizations/lemonade-sdk/settings/actions/runners/new 1. You should see your runner show up in the `stx` runner group in the lemonade-sdk org ### Runner Setup These steps will use GitHub Actions to run automated setup and validation for your new runner while it is still in the `stx-test` group. 1. Go to the [lemonade ryzenai test action](https://github.com/lemonade-sdk/lemonade/actions/workflows/test_ryzenai.yml) and click "run workflow". - Select `stx-test` as the runner group - Click `Run workflow` 1. The workflow should appear at the top of the queue. Click into it. - Expand the `Set up job` section and make sure `Runner name:` refers to your new runner. Otherwise, the job may have gone to someone else's runner in the test group. You can re-queue the workflow until it lands on your runner. - Wait for the workflow to finish successfully. 1. Repeat step 1. Wait for it to finish successfully. Congrats, your new runner is working! 1. Go to the stx Runner Group, click your new runner, and click the gear icon to change labels. Uncheck `stx-test` and check `stx`. 1. Done! ## Maintenance and Troubleshooting This is a production system and things will go wrong. Here is some advice on what to do. ### Check your runner's status You can run `Get-EventLog -LogName Application -Source ActionsRunnerService` in a powershell terminal on your runner to get more information about what it's been up to. If there have been any problems recently, they may show up like: - Error: Runner connect error: < details about the connection error > - Information: Runner reconnected - Information: Running Job: < job name > - Information: Job < job name > completed with result: [Succeeded / Canceled / Failed] ### Actions are failing unexpectedly Actions fail all the time, often because they are testing buggy code. However, sometimes an Action will fail because something is wrong with the specific runner that ran the Action. If this happens to you, here are some steps you can take (in order): 1. Take note of which runner executed your Action. You can check this by going to the `Set up job` section of the Action's log and checking the `Runner name:` field. The machine name in that field will correspond to a machine on the [runners page](https://github.com/organizations/lemonade-sdk/settings/actions/runners). 1. Re-queue your job. It is possible that that the failure is a one-off, and it will work the next time on the same runner. Re-queuing also gives you a chance of getting a runner that is in a healthier state. 1. If the same runner is consistently failing, it is probably in an unhealthy state (or you have a bug in your code and you're just blaming the runner). If a runner is in an unhealthy state: 1. [Take the laptop offline](#take-a-laptop-offline) so that it stops being allocated Actions. 1. [Open an Issue](https://github.com/lemonade-sdk/lemonade/issues/new). Assign it to the maintainer of the laptop (their name should be in the runner's name). Link the multiple failed workflows that have convinced you that this runner is unhealthy. 1. Re-queue your job. You'll definitely get a different runner now since you took the unhealthy runner offline. 1. If all runners are consistently failing your workflow, seriously think about whether your code is the problem. ### Take a laptop offline If you need to do some maintenance on your laptop, use it for dev/demo work, etc. you can remove it from the runners pool. Also, if someone else's laptop is misbehaving and causing Actions to fail unexpectedly, you can remove that laptop from the runners pool to make sure that only healthy laptops are selected for work. There are three options: Option 1, which is available to anyone in the `lemonade-sdk` org: remove the `rai300_400` label from the runner. - Workflows use `runs-on: rai300_400` to target runners with the `rai300_400` label. Removing this label from the runner will thus remove the runner from the pool. - Go to the [runners page](https://github.com/organizations/lemonade-sdk/settings/actions/runners), click the specific runner in question, click the gear icon in the Labels section, and uncheck `rai300_400`. - To reverse this action later, go back to the [runners page](https://github.com/organizations/lemonade-sdk/settings/actions/runners), click the gear icon, and check `rai300_400`. Option 2, which requires physical/remote access to the laptop: - In a PowerShell terminal, run `Stop-Service "actions.runner.*"`. - To reverse this action, run `Start-Service "actions.runner.*"`. Option 3 is to just turn the laptop off :) ## Creating Workflows GitHub Workflows define the Actions that run on self-hosted laptops to perform testing and experimentation tasks. This section will help you learn about what capabilities are available and show some examples of well-formed workflows. ### Capabilities and Limitations Because we use self-hosted systems, we have to be careful about what we put into these workflows so that we avoid: - Corrupting the laptops, causing them to produce inconsistent results or failures. - Over-subscribing the capacity of the available laptops Here are some general guidelines to observe when creating or modifying workflows. If you aren't confident that you are properly following these guidelines, please contact someone to review your code before opening your PR. - Place a 🌩️ emoji in the name of all of your self-host workflows, so that PR reviewers can see at a glance which workflows are using self-hosted resources. - Example: `name: Test Lemonade on NPU and Hybrid with OGA environment 🌩️` - Avoid triggering your workflow before anyone has had a chance to review it against these guidelines. To avoid triggers, do not include `on: pull request:` in your workflow until after a reviewer has signed off. - Only map a workflow with `runs on: rai300_400` if it actually requires Ryzen AI compute. If a step in your workflow can use generic compute (e.g., running a Hugging Face LLM on CPU), put that step on a generic non-self-hosted runner like `runs on: windows-latest`. - Be very considerate about installing software on to the runners: - Installing software into the CWD (e.g., a path of `.\`) is always ok, because that will end up in `C:\actions-runner\_work\REPO`, which is always wiped between tests. - Installing software into `AppData`, `Program Files`, etc. is not advisable because that software will persist across tests. See the [setup](#npu-runner-setup) section to see which software is already expected on the system. - Always create new virtual environments in the CWD, for example `python -m venv .venv`. - This way, the virtual environment is located in `C:\actions-runner\_work\REPO`, which is wiped between tests. - Make sure to activate your virtual environment before running any `pip install` commands. Otherwise your workflow will modify the system Python installation! - PowerShell scripts do not necessarily raise errors by programs they call. - That means PowerShell can call a Python test, and then keep going and claim "success" even if that Python test fails and raises an error (non-zero exit code). - You can add `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }` after any line of script where it is that is particularly important to fail the workflow if the program in the preceding line raised an error. - For example, this will make sure that lemonade installed correctly: 1. pip install -e . 2. if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - Be considerate of how long your workflow will run for, and how often it will be triggered. - All workflows go into the same queue and share the same pool of runners. - A good target length for a workflow is 15 minutes. - Be considerate of how much data your workflow will download. - It would be very bad to fill up a hard drive, since Windows machines misbehave pretty bad when their drives are full. - Place your Hugging Face cache inside the `_work` directory so that it will be wiped after each job. - Example: `$Env:HF_HOME=".\hf-cache"` - Place your Lemonade cache inside the `_work` directory so that it will be wiped after each job. - Example: `lemonade-eval -d .\ci-cache` or `$Env:LEMONADE_CACHE_DIR=".\ci-cache"`. Use the environment variable, rather than the `-d` flag, wherever possible since it will apply to all lemonade-eval calls within the job step. # License [Apache 2.0 License](../LICENSE) Copyright(C) 2024-2025 Advanced Micro Devices, Inc. All rights reserved. lemonade-sdk-lemonade-d88f5d9/docs/server/000077500000000000000000000000001515430344400205355ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/docs/server/README.md000066400000000000000000000070501515430344400220160ustar00rootroot00000000000000# Getting Started with Lemonade Server 🍋 Lemonade Server is a server interface that uses the standard Open AI API, allowing applications to integrate with local LLMs. This means that you can easily replace cloud-based LLMs with private and free LLMs that run locally on your own PC's NPU and GPU. Lemonade Server is available as a standalone tool with a [one-click Windows GUI installer](https://github.com/lemonade-sdk/lemonade/releases/latest/download/lemonade-server-minimal.msi). Installers are also available for [Linux](https://lemonade-server.ai/install_options.html#linux) and [macOS (beta)](https://lemonade-server.ai/install_options.html#macos). ## Intro Video ▶️ [Watch on YouTube](https://www.youtube.com/watch?v=mcf7dDybUco) Once you've installed, we recommend checking out these resources: | Documentation | Description | |---------------|-------------| | [Supported Applications](./apps/README.md) | Explore applications that work out-of-the-box with Lemonade Server. | | [Lemonade Server Concepts](./concepts.md) | Background knowledge about local LLM servers and the OpenAI standard. | | [`lemonade-server` CLI Guide](./lemonade-server-cli.md) | Learn how to manage the server process and install new models using the command-line interface. | | [Models List](https://lemonade-server.ai/models.html) | Browse a curated set of LLMs available for serving. | | [Server Spec](./server_spec.md) | Review all supported OpenAI-compatible and Lemonade-specific API endpoints. | | [Integration Guide](./server_integration.md) | Step-by-step instructions for integrating Lemonade Server into your own applications. | > Note: if you want to develop Lemonade Server itself, you can [install from source](https://lemonade-server.ai/install_options.html). ## Integrate Lemonade Server with Your Application Since Lemonade Server implements the standard OpenAI API specification, you can use any OpenAI-compatible client library by configuring it to use `http://localhost:8000/api/v1` as the base URL. A table containing official and popular OpenAI clients on different languages is shown below. Feel free to pick and choose your preferred language. | Python | C++ | Java | C# | Node.js | Go | Ruby | Rust | PHP | |--------|-----|------|----|---------|----|-------|------|-----| | [openai-python](https://github.com/openai/openai-python) | [openai-cpp](https://github.com/olrea/openai-cpp) | [openai-java](https://github.com/openai/openai-java) | [openai-dotnet](https://github.com/openai/openai-dotnet) | [openai-node](https://github.com/openai/openai-node) | [go-openai](https://github.com/sashabaranov/go-openai) | [ruby-openai](https://github.com/alexrudall/ruby-openai) | [async-openai](https://github.com/64bit/async-openai) | [openai-php](https://github.com/openai-php/client) | ### Python Client Example ```python from openai import OpenAI # Initialize the client to use Lemonade Server client = OpenAI( base_url="http://localhost:8000/api/v1", api_key="lemonade" # required but unused ) # Create a chat completion completion = client.chat.completions.create( model="Llama-3.2-1B-Instruct-Hybrid", # or any other available model messages=[ {"role": "user", "content": "What is the capital of France?"} ] ) # Print the response print(completion.choices[0].message.content) ``` For more detailed integration instructions, see the [Integration Guide](./server_integration.md). lemonade-sdk-lemonade-d88f5d9/docs/server/apps/000077500000000000000000000000001515430344400215005ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/docs/server/apps/README.md000066400000000000000000000010151515430344400227540ustar00rootroot00000000000000# App Integration Guides This folder contains integration guides for connecting third-party applications to Lemonade Server. ## View the Marketplace For a complete list of compatible apps with links to guides, videos, and more, visit the **[Lemonade Marketplace](https://lemonade-server.ai/marketplace)**. ## Contributing If you've connected Lemonade to a new application and would like to contribute a guide, see our [contribution guide](../../contribute.md) or email us at [lemonade@amd.com](mailto:lemonade@amd.com). lemonade-sdk-lemonade-d88f5d9/docs/server/apps/ai-dev-gallery.md000066400000000000000000000075411515430344400246330ustar00rootroot00000000000000# AI Dev Gallery with Lemonade Server ## Overview [AI Dev Gallery](https://aka.ms/ai-dev-gallery) is Microsoft's showcase application that demonstrates various AI capabilities through built-in samples and applications. It provides an easy way to explore and experiment with different AI models and scenarios, including text generation, chat applications, and more. AI Dev Gallery has native integration with Lemonade Server, which means it can automatically detect and connect to your local Lemonade instance without manual URL configuration. ## Expectations AI Dev Gallery works well with most models available in Lemonade. The built-in samples are designed to work with various model types and sizes, making it a great tool for testing and exploring different AI capabilities locally. The application provides a user-friendly interface for experimenting with AI models through pre-built scenarios, making it accessible for both beginners and advanced users. ## Setup ### Prerequisites 1. Install Lemonade Server by following the [Lemonade Server Instructions](../README.md) and using the installer .exe. 2. **Important**: Make sure your Lemonade Server is running before opening AI Dev Gallery. ### Install AI Dev Gallery 1. Open the Microsoft Store on Windows. 2. Search for "AI Dev Gallery" by Microsoft Corporation. 3. Click "Install" to download and install the application. Alternatively, you can access AI Dev Gallery directly through [aka.ms/ai-dev-gallery](https://aka.ms/ai-dev-gallery). ### Connect to Lemonade AI Dev Gallery has native integration with Lemonade Server, so no manual configuration is required. The application will automatically detect your running Lemonade Server instance. **Important**: Ensure your Lemonade Server is running before launching AI Dev Gallery. ## Usage AI Dev Gallery provides various built-in applications and samples to explore AI capabilities: ### Quick Start 1. Launch AI Dev Gallery. 2. Navigate to **Samples** → **Text** → **Chat** (or another text/code sample). 3. Click on the model selector above the chat window. 4. Select **Lemonade** from the available providers. 5. Choose your preferred model from the list of available models. ### Supported Scenarios AI Dev Gallery supports various AI scenarios through its sample applications with Lemonade integration: **Text Processing**: - **Conversational AI**: Chat and Semantic Kernel Chat for interactive conversations - **Content Generation**: Generate text for various purposes and creative writing - **Language Tasks**: Translation, grammar checking, and paraphrasing - **Text Analysis**: Sentiment analysis and content moderation - **Information Retrieval**: Semantic search and retrieval augmented generation - **Text Enhancement**: Summarization and custom parameter configurations **Code Assistance**: - **Code Generation**: Create code snippets and programs - **Code Analysis**: Explain existing code and understand functionality ### Tips for Best Experience - Start your Lemonade Server before opening AI Dev Gallery - Try different models to see how they perform across various scenarios - Explore different sample categories to understand various AI capabilities - Use the built-in samples as starting points for your own AI experiments ## Troubleshooting ### AI Dev Gallery doesn't detect Lemonade - Ensure Lemonade Server is running and accessible at `http://localhost:8000` - Restart AI Dev Gallery after ensuring Lemonade Server is running ### Models not appearing in the selector - Open `http://localhost:8000` in a browser and make sure to download the models you want to use through the "Model Manager" tab. ## Additional Resources - [AI Dev Gallery Website](https://aka.ms/ai-dev-gallery) - [Lemonade Server Models](https://lemonade-server.ai/models.html) lemonade-sdk-lemonade-d88f5d9/docs/server/apps/ai-toolkit.md000066400000000000000000000062471515430344400241070ustar00rootroot00000000000000# Microsoft AI Toolkit for VS Code ## Overview The [AI Toolkit for Visual Studio Code](https://learn.microsoft.com/en-us/windows/ai/toolkit/) is a VS Code extension that simplifies generative AI app development by bringing together cutting-edge AI development tools and models from various catalogs. It supports running AI models locally or connecting to remote models via API keys. ## Demo Video ▶️ [Watch on YouTube](https://www.youtube.com/watch?v=JecpotOZ6qo) ## Expectations We have found that most LLMs work well with this application. However, the `Inference Parameters` option is not fully supported, as Lemonade Server currently does not accept those as inputs (see [server_spec.md](../server_spec.md) for details). ## Setup ### Prerequisites 1. Install Lemonade Server by following the [Lemonade Server Instructions](../README.md) and using the installer .exe. ### Install AI Toolkit for VS Code 1. Open the Extensions tab in VS Code Activity Bar. 2. Search for "AI Toolkit for Visual Studio Code" in the Extensions Marketplace search bar. 3. Select the AI Toolkit extension and click install. This will add an AI Toolkit icon to your VS Code Activity Bar. ### Connect Lemonade to AI Toolkit The AI Toolkit now supports "Bring Your Own Model" functionality, allowing you to connect to models served via the OpenAI API standard, which Lemonade uses. 1. Open the AI Toolkit tab in your VS Code Activity Bar. 2. In the right corner of the "My Models" section, click the "+" button to "Add model for remote inference". 3. Select "Add a custom model". 4. When prompted to "Enter OpenAI chat completion endpoint URL" enter: ``` http://localhost:8000/api/v1/chat/completions ``` 5. When prompted to "Enter the exact model name as in the API" select a model (e.g., `Phi-3-Mini-Instruct-Hybrid`) - Note: You can get a list of all models available [here](https://lemonade-server.ai/models.html). 6. Select the same name as the display model name. 7. Skip the HTTP authentication step by pressing "Enter". ## Usage Once you've set up the Lemonade model in AI Toolkit, you can: 1. Use the **AI Playground** tool to directly interact with your added model. 2. Use the **Prompt Builder** tool to craft effective prompts for your AI models. 3. Use the **Bulk Run** tool to compute responses for custom datasets and easily visualize those responses on a table format. 4. Use the **Evaluation** tool to quickly assess your model's coherence, fluency, relevance, and similarity, as well as to compute BLEU, F1, GLEU, and Meteor scores. ## Additional Resources - [AI Toolkit for VS Code Documentation](https://learn.microsoft.com/en-us/windows/ai/toolkit/) - [AI Toolkit GitHub Repository](https://github.com/microsoft/vscode-ai-toolkit) - [Bring Your Own Models on AI Toolkit](https://techcommunity.microsoft.com/blog/azuredevcommunityblog/bring-your-own-models-on-ai-toolkit---using-ollama-and-api-keys/4369411) lemonade-sdk-lemonade-d88f5d9/docs/server/apps/anythingLLM.md000066400000000000000000000101361515430344400242110ustar00rootroot00000000000000 # Running agents locally with Lemonade and AnythingLLM ## Overview [AnythingLLM](https://github.com/Mintplex-Labs/anything-llm) is a versatile local LLM platform that allows you to chat with your documents and code using a variety of models. It supports the OpenAI-compatible API interface, allowing easy integration with local servers like Lemonade. This guide will help you configure AnythingLLM to use Lemonade's OpenAI-compatible server, and utilize the powerful `@agent` capability to interact with documents, webpages, and more. ## Expectations Lemonade integrates best with AnythingLLM when using models such as `Qwen-1.5-7B-Chat-Hybrid` and `Llama-3.2-1B-Instruct-Hybrid`, both of which support a context length of up to 3,000 tokens. Keep in mind that when using the `@agent` feature, multi-turn conversations can quickly consume available context. As a result, the number of back-and-forth turns in a single conversation may be limited due to the growing context size. ## Setup ### Prerequisites 1. Install Lemonade Server by following the [Lemonade Server Instructions](../README.md) and using the installer .exe. 2. Install and set up AnythingLLM from their [GitHub](https://github.com/Mintplex-Labs/anything-llm#quick-start) or [website](https://anythingllm.com/desktop). ### Configure AnythingLLM to Use Lemonade
  1. In the bottom of the left menu, click on the wrench icon to "Open Settings".
  2. Under the menu "AI Providers", click "LLM".
  3. Select "Generic OpenAI" and enter the following info:
    SettingValue
    Base URLhttp://localhost:8000/api/v1
    API Key-
    Chat Model NameQwen-1.5-7B-Chat-Hybrid
    Token context window3000
    Max Tokens3000
  4. In the bottom left, click the back button to exit.
  5. In the left menu, click "New Workspace" and give it a name.
  6. Where you see your new workspace, click the gear icon to open the "Workspace Settings"
  7. In the top menu of the window that opens, click on "Agent Configuration"
  8. Under Chat Settings, select Generic OpenAI and click save.
  9. Under Workspace Agent LLM Provider, select "Generic OpenAI" and click save.
## Usage with @agent ### Overview Agents are capable of scraping websites, listing and summarizing documents, searching the web, creating charts, and even saving files to your desktop or their own memory. To start an agent session, simply go to any workspace and type `@agent `. To exit the session, just type `exit`. ### Agent Skills You may turn on and off specific `Agent Skills` by going to your `Workspace Settings` → `Agent Configuration` → `Configure Agent Skills`. Available agent skills include: * RAG & long-term memory * View and summarize documents * Scrape Websites * Generate & save files to browser * Generate Charts * Web Search * SQL Connector ### Examples Here are some examples on how you can interact with Anything LLM agents: - **Rag & long-term memory** - `@agent My name is Dr Lemon. Remember this in our next conversation` - Then, on a follow up chat you can ask `@agent What is my name according to your memory?` - **Scrape Websites** - `@agent Scrape this website and tell me what are the two ways of installing lemonade https://github.com/lemonade-sdk/lemonade/blob/main/docs/server/README.md` - **Web Search** (enable skill before trying) - `@agent Search the web for the best place to buy shoes` You can find more details about agent usage [here](https://docs.anythingllm.com/agent/usage). ## Additional Resources - [AnthingLLM Website](https://anythingllm.com/) - [AnythingLLM GitHub](https://github.com/Mintplex-Labs/anything-llm) - [AnythingLLM Documentation](https://docs.anythingllm.com/) lemonade-sdk-lemonade-d88f5d9/docs/server/apps/codeGPT.md000066400000000000000000000055161515430344400233160ustar00rootroot00000000000000# CodeGPT with VS Code ## Overview [CodeGPT Chat](https://codegpt.co/) is an AI-powered chatbot designed to assist developers with coding tasks directly within their preferred integrated development environments (IDEs), for example, VS Code. ## Expectations We have found that the `Qwen-1.5-7B-Chat-Hybrid` model is the best Hybrid model available for coding. It is good at chatting with a few files at a time in your codebase to learn more about them. It can also make simple code editing suggestions pertaining to a few lines of code at a time. However, we do not recommend using this model for analyzing large codebases at once or making large or complex file edits. ## Setup ### Prerequisites 1. Install Lemonade Server by following the [Lemonade Server Instructions](../README.md) and using the installer .exe. ### Install CodeGPT in VS Code > The following instructions are based off CodeGPT provided instructions found [here](https://docs.codegpt.co/docs/tutorial-basics/installation). 1. Open the Extensions tab in VS Code Activity Bar. 1. Search "CodeGPT: Chat & AI Agents" in the Extensions Marketplace search bar. 1. Select the CodeGPT extension and click install. This will add a CodeGPT tab to your VS Code Activity Bar. ### Add Lemonade Server to CodeGPT > Note: The following instructions are based on instructions from CodeGPT found [here](https://docs.codegpt.co/docs/tutorial-ai-providers/custom).
  1. Open the CodeGPT tab in your VS Code Activity Bar.
  2. Sign Up or Sign into your account.
  3. In the model dropdown menu and click "View More".
  4. Select the tab: "LLMs Cloud model"
  5. Under "All Models", set the following:
    FieldValue
    Select Provider:Custom
    Select Model: Qwen-1.5-7B-Chat-Hybrid
  6. Click "Change connection settings" and enter the following information:
    FieldValue
    API Key-
    Custom Linkhttp://localhost:8000/api/v1/api/v1
## Usage > Note: see the CodeGPT [user guide](https://docs.codegpt.co/docs/intro) to learn about all of their features. To try out CodeGPT: - Open the CodeGPT tab in your VS Code Activity Bar, and in the chat box, type a question about your code. Use the `#` symbol to specify a file. - Example: "What's the fastest way to install lemonade in #getting_started.md?" - Use /Fix to find and fix a minor bug. - Use /Document to come up with docstrings and comments for a file. - Use /UnitTest to make a test file. lemonade-sdk-lemonade-d88f5d9/docs/server/apps/continue.md000066400000000000000000000233121515430344400236470ustar00rootroot00000000000000# Continue Coding Assistant [Continue](https://www.continue.dev/) provides open-source Integrated Development Environment (IDE) extensions, such as for [Visual Studio Code](https://code.visualstudio.com/) and [JetBrains](https://www.jetbrains.com/ides/), and an open-source CLI that lets developers leverage custom AI coding agents. This guide walks through how to use Lemonade Server with the Continue VS Code extension for code generation, editing, and chat capabilities, all running locally on your AMD PC. ## Prerequisites Before you start, make sure you have the following: ### Software Requirements - **IDE**: [Visual Studio Code (v1.80+)](https://code.visualstudio.com/) or another supported IDE. - **Lemonade Server**: Installed and set up using the [Getting Started guide](https://lemonade-server.ai/docs/server/). - **Lemonade Server Running**: The server should be running at `http://localhost:8000`. If you change the port in Lemonade Server (e.g., to 8020, 8040, etc.), you'll need to update the API Base URL in Continue's configuration to match the same port. - **Model Downloaded**: At least one model from the [supported models list](https://lemonade-server.ai/models.html) must be installed locally. ### Hardware Requirements For best results, a code-tuned model with at least 20B parameters is required. To run such a model: * **Minimum spec**: PC with an integrated GPU (Ryzen™ AI 7000-series or newer) and 64 GB system RAM. * **Recommended specs**: * PC with a discrete GPU that has 16 GB VRAM or greater (Radeon™ 7800 XT or newer). * Strix Halo PC with 64 GB System RAM or greater. ## Setup ### Configuring Lemonade Server with Continue 1. **Install Models Locally** - Use the Model Manager or [lemonade-server CLI](https://lemonade-server.ai/docs/server/lemonade-server-cli/) to download your desired model, for example: ```bash lemonade-server pull ``` _Example downloading Qwen3-Coder:_ ```bash lemonade-server pull Qwen3-Coder-30B-A3B-Instruct-GGUF ``` 2. **Start Lemonade Server**: Ensure Lemonade Server is running at `http://localhost:8000`. You can start it from the Lemon tray icon or by running: ```bash lemonade-server serve ``` 3. **Verify Model is Loaded**: Use the Model Manager or tray icon to confirm your model is loaded and ready. Continue will automatically detect Lemonade Server running on localhost. ### Setting Up Continue Extension in VS Code 1. **Go to Extensions Marketplace**: In VS Code, click the Extensions icon in the Activity bar (default is on the left). 2. **Add "Continue"**: Type "Continue" in the search box. Click "Install" on the Continue extension entry. _Example marketplace screen:_ ![Continue Extension in VS Code Marketplace](https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/guides/continue/continue_vscode_marketplace.png) 3. **Open Continue in VS Code**: After installation completes, the Continue logo appears in the Activity bar. Click it to open the extension. 4. **Add Lemonade Server Provider**: Click the model dropdown menu in the Continue sidebar, then select "Add Chat Model". Choose "Lemonade Server" from the list of available providers. Continue will set the default address to `http://localhost:8000`, but it can be changed to match a different setup. _Example configuration screen:_ ![Add Lemonade Provider](https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/guides/continue/continue_add_lemonade_provider.png) 5. **Select Your Model**: Once Lemonade Server is added, use the drop-down menu to select the model you downloaded earlier (e.g., `Qwen3-Coder-30B-A3B-Instruct-GGUF`). _Example model selection:_ ![Model Selection Dropdown](https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/guides/continue/continue_model_selection_dropdown.png) ## Working with Continue.dev Continue provides three interaction modes for different development tasks: 1. **Chat**: Code explanations, debugging discussions, architecture planning 2. **Plan**: Provides a safe environment with read-only tools for exploring code and planning changes 3. **Agent**: Multi-file refactoring, large-scale changes across projects ![Continue Modes Interface](https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/docs/guides/continue/continue_extension_modes.png) See the [Continue Documentation](https://docs.continue.dev/getting-started/overview) for detailed descriptions. ## Examples ### Example 1: Chat Mode - Building an Asteroids Game In this example, we'll use `Qwen3-Coder-30B-A3B-Instruct-GGUF` model to build a Python game. **Input**: I want to create an asteroids game using PyGame. What guidelines should I follow in the code to do so? ![Continue Chat Asteroids](https://github.com/lemonade-sdk/assets/blob/main/docs/guides/continue/continue_chat_asteroids01.png?raw=true) The model provides a basic framework for an Asteroids game. You can then prompt it to provide you some sample code to get started. **Input**: Provide me a basic implementation to get started. ![Continue Chat Asteroids Example Code](https://github.com/lemonade-sdk/assets/blob/main/docs/guides/continue/continue_chat_asteroids02.png?raw=true) In the top-right corner, you can click the "Create file" to move the code from the chat window to a Python file and save it. To run, install `pygame` and execute the code with `python main.py`. ### Example 2: Plan Mode - Analysis of the Game In this example, we'll use Plan mode to have the LLM analyze your code and provide feedback. Plan mode reviews your code and suggests improvements, but does not modify your files. **To use Plan mode with large files, increase Lemonade Server's context size:** 1. **Stop Lemonade Server**: Use the tray icon to "Quit Lemonade" or close any running Lemonade Server processes. 2. **Restart with higher context size**: Open a terminal and run: ```bash lemonade-server serve --ctx-size 8192 ``` 3. **Use Plan mode in VS Code**: Select the "Plan" option in Continue, enter your prompt and press Alt+Enter to include the currently active file as context. **Input**: What improvements could be made to this game? ![Continue Plan Asteroids](https://github.com/lemonade-sdk/assets/blob/main/docs/guides/continue/continue_plan_asteroids.png?raw=true) ### Example 3: Agent Mode - Improving the Game Lastly, we'll use Agent Mode to take action to change the code to implement improvements. ![Continue Agent Asteroids](https://github.com/lemonade-sdk/assets/blob/main/docs/guides/continue/continue_agent_asteroids.png?raw=true) Here, we can see that the agent edited the code in `main.py` to improve the gameplay and add colors. ## Best Practices ### Setup & Configuration - **Install Lemonade Server**: Follow the [setup guide](https://lemonade-server.ai/docs/server/) to install and configure Lemonade Server before you begin development. - **Download Models Locally**: Use `lemonade-server pull ` to install models you want to use. Refer to the [supported models list](https://lemonade-server.ai/models.html) for available options. - **Pre-load Models**: Start Lemonade Server and load your models before coding sessions. This can easily be done using the Lemon tray icon and `Load`. - **Increase Context Size for Agent Mode**: For large code changes with GGUF models, start Lemonade Server with a higher context size: ```bash lemonade-server serve --ctx-size 8192 ``` - **Customize Scoping**: See [Continue Customization](https://docs.continue.dev/customization/overview) for tips on effective model configuration and scoping. ### Development Workflow - **Start New Conversations for Each Feature**: Begin a fresh chat for every new feature or task. Clear chat history when switching topics to keep interactions focused. - **Keep Prompts Focused**: Only include the code and context relevant to your current task. This helps the model provide accurate and useful responses. - **Write Clear, Detailed Prompts**: Structure your requests with a clear task description, specific requirements, and any technical constraints. - **Use Agent Mode for Multi-File Changes**: Invoke agent mode with the `@` symbol to perform refactoring or changes across multiple files. - **Be Specific in Your Requests**: Move from broad prompts ("Create a game") to detailed ones ("Create an Asteroids game in Python using Pygame, under 300 lines, with ship controls and asteroid splitting"). - **Iterate and Test Frequently**: Generate an initial implementation, test it right away, and refine with targeted follow-up prompts. - **Leverage Unlimited Iterations**: With local models, you can iterate as many times as needed for continuous improvement. ## Common Issues **Model not appearing in Continue** - Make sure Lemonade Server is running and the model is loaded locally. - Double-check the [supported models list](https://lemonade-server.ai/models.html) and install any missing models with: ```bash lemonade-server pull ``` **Slow response times** - Pre-load your model before starting coding sessions. - Check your system's available RAM and close unused applications to free up resources. **Missing error handling in generated code** - In your prompt, explicitly request: "with comprehensive error handling" to ensure the model adds proper error checks. **Inconsistent code style** - Provide a sample or example of your desired code style in your prompt. The model will use this as a reference for formatting. ## Resources - [Lemonade Server Setup Guide](https://lemonade-server.ai/docs/server/) - [Lemonade Server Supported Models](https://lemonade-server.ai/models.html) - [Lemonade Applications](https://lemonade-server.ai/docs/server/apps/) - [Continue Documentation](https://docs.continue.dev) lemonade-sdk-lemonade-d88f5d9/docs/server/apps/mindcraft.md000066400000000000000000001002041515430344400237660ustar00rootroot00000000000000# Mindcraft ## Overview Mindcraft is an open-source project that creates Minecraft bots powered by large language models (LLMs) to engage with the game and its players. This readme will demonstrate how to integrate Lemonade to use local LLMs with Mindcraft. ## Expectations We found the `Qwen-1.5-7B-Chat-Hybrid` model to be the most effective for this task, delivering fast responses with higher accuracy. However, as a smaller model running locally, with a limited context length, it may occasionally struggle with certain requests—for instance, it might attempt to build a structure, but the result may not be correct. For more detailed information, please refer to the [Data Insights](#data-insights) section. ## Setup ### Prerequisites 1. Install Lemonade Server by following the [Lemonade Server Instructions](../README.md) and using the installer .exe. 1. Obtain a copy of [Minecraft](https://www.minecraft.net/en-us) from Microsoft. 1.20.4 of the JAVA Edition is required for this. You can obtain that version [by following these instructions](https://www.minecraft.net/en-us/article/minecraft-java-edition-1-20-4). 1. Install [Node.js](https://nodejs.org/en/download) (at least v14). ### Environment Setup Clone [Mindcraft](https://github.com/kolbytn/mindcraft) from GitHub: * `git checkout 07ea071ac3b0d4954d62b09d881c38a06bc2a589`: this will ensure the code base is equal to where this test was performed. In the clone of the `mindcraft` repository: * Rename the file `keys.example.json` to `keys.json`. You do not need to edit the contents of this JSON file. * Update `keys.json` by adding any text to the value of `OPENAI_API_KEY`. ```json { "OPENAI_API_KEY": "", "OPENAI_ORG_ID": "", "GEMINI_API_KEY": "", "ANTHROPIC_API_KEY": "", "REPLICATE_API_KEY": "", "GROQCLOUD_API_KEY": "", "HUGGINGFACE_API_KEY": "", "QWEN_API_KEY": "", "XAI_API_KEY": "", "MISTRAL_API_KEY": "", "DEEPSEEK_API_KEY": "", "NOVITA_API_KEY": "", "OPENROUTER_API_KEY": "" } ``` * In a terminal/command prompt, run `npm install` from the cloned Mindcraft directory. * Replace the contents of the file `andy.json` with the following: ```json { "name": "Andy", "model": { "model": "Qwen-1.5-7B-Chat-Hybrid", "url": "http://localhost:8000/api/v1", "params": { "temperature": 0.5 } }, "modes": { "self_preservation": true, "unstuck": true, "cowardice": false, "self_defense": true, "hunting": true, "item_collecting": true, "torch_placing": true, "elbow_room": true, "idle_staring": true, "cheat": true } } ``` * Find the line in `src/models/prompter.js` that says: ```javascript else if (profile.model.includes('gpt') || profile.model.includes('o1')|| profile.model.includes('o3')) ``` * ... and replace it with the following: ```javascript else if ([ 'Qwen-1.5-7B-Chat-Hybrid', 'Llama-3.2-1B-Instruct-Hybrid' ].includes(profile.model) || profile.model.includes('gpt') || profile.model.includes('o1') || profile.model.includes('o3')) ``` ### Launching Everything * Start Lemonade Server by double-clicking the desktop icon 🍋. * Start a Minecraft world and open it to LAN on localhost port `55916`. * This is done by pressing the ESC button to open the menu, then click "Open to LAN" and enter the Port Number: `55916`. * Click "Start LAN World". For instructions on how Open to LAN, see the section "Hosting a LAN Server" for the JAVA Edition in this [wiki article](https://minecraft.wiki/w/Tutorial:Setting_up_a_LAN_world). * Run `node main.js` from the installed directory. * In Minecraft, to give the agent commands, press `t` and enter the command. For example: * "come here" * "hunt pigs" - you and the agent must be close to some pigs to do this. ### Model Configurations Lemonade models tested: * Llama-3.2-1B-Instruct-Hybrid * **Qwen-1.5-7B-Chat-Hybrid 👍** ## Challenges and Observations 1. The current MindCraft configuration has a tendency to send very large context that is resent. The context will include examples of behaviors that may not be necessary, generating over 2100 input tokens for simple commands. Further testing would be required to understand model behavior based on reduced context size. 1. Frequent token limit breaches resulting in timeouts or incomplete responses due to the aforementioned context size problem. Once the token maximum content ceiling is raised, further testing would be prudent using the same `Qwen-1.5-7B-Chat-Hybrid` model as a baseline and using it to test other models such as DeepSeek and Llama variants. content 1. High GPU resource consumption during model inference, impacting system performance. ## Results The `Qwen-1.5-7B-Chat-Hybrid` model showed the most potential, responding to commands and attempting construction tasks, though with limited accuracy. ## Recommendations 1. Optimize examples sent to Lemonade for conciseness to reduce token usage. 1. Reduce input context to prevent model overload. ## Data Insights The following are examples of requests made by the Mindcraft software to the Lemonade Server and how the tokens were interpreted. These examples are taken from an initial game stage, including the first request sent to the Lemonade Server and a subsequent user chat that says, "come here." The purpose is to show how large the context is that is being sent. This could be optimized for more efficient performance and results. * Initial Payload ```json {"model":"Qwen-1.5-7B-Chat-Hybrid","messages":[{"role":"system","content":"You are a playful Minecraft bot named LLama that can converse with players, see, move, mine, build, and interact with the world by using commands. Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer('playername', 3)'. This is extremely important to me, take a deep breath and have fun :)\n\n\nSTATS\n- Position: x: 6.50, y: -60.00, z: 28.50\n- Gamemode: creative\n- Health: 20 / 20\n- Hunger: 20 / 20\n- Biome: plains\n- Weather: Clear\n- Block Below: grass_block\n- Block at Legs: air\n- Block at Head: air\n- First Solid Block Above Head: none\n- Time: Afternoon- Current Action: Idle\n- Nearby Human Players: Transpier\n- Nearby Bot Players: None.\nAgent Modes:\n- self_preservation(ON)\n- unstuck(ON)\n- cowardice(ON)\n- self_defense(ON)\n- hunting(ON)\n- item_collecting(ON)\n- torch_placing(ON)\n- elbow_room(ON)\n- idle_staring(ON)\n- cheat(ON)\n\n\n\nINVENTORY: Nothing\nWEARING: Nothing\n\n\n*COMMAND DOCS\n You can use the following commands to perform actions and get information about the world. \n Use the commands with the syntax: !commandName or !commandName(\"arg1\", 1.2, ...) if the command takes arguments.\n\n Do not use codeblocks. Use double quotes for strings. Only use one command in each response, trailing commands and comments will be ignored.\n!stats: Get your bot's location, health, hunger, and time of day.\n!inventory: Get your bot's inventory.\n!nearbyBlocks: Get the blocks near the bot.\n!craftable: Get the craftable items with the bot's inventory.\n!entities: Get the nearby players and entities.\n!modes: Get all available modes and their docs and see which are on/off.\n!savedPlaces: List all saved locations.\n!getCraftingPlan: Provides a comprehensive crafting plan for a specified item. This includes a breakdown of required ingredients, the exact quantities needed, and an analysis of missing ingredients or extra items needed based on the bot's current inventory.\nParams:\ntargetItem: (string) The item that we are trying to craft\nquantity: (number) The quantity of the item that we are trying to craft\n!help: Lists all available commands and their descriptions.\n!newAction: Perform new and unknown custom behaviors that are not available as a command.\nParams:\nprompt: (string) A natural language prompt to guide code generation. Make a detailed step-by-step plan.\n!stop: Force stop all actions and commands that are currently executing.\n!stfu: Stop all chatting and self prompting, but continue current action.\n!restart: Restart the agent process.\n!clearChat: Clear the chat history.\n!goToPlayer: Go to the given player.\nParams:\nplayer_name: (string) The name of the player to go to.\ncloseness: (number) How close to get to the player.\n!followPlayer: Endlessly follow the given player.\nParams:\nplayer_name: (string) name of the player to follow.\nfollow_dist: (number) The distance to follow from.\n!goToCoordinates: Go to the given x, y, z location.\nParams:\nx: (number) The x coordinate.\ny: (number) The y coordinate.\nz: (number) The z coordinate.\ncloseness: (number) How close to get to the location.\n!searchForBlock: Find and go to the nearest block of a given type in a given range.\nParams:\ntype: (string) The block type to go to.\nsearch_range: (number) The range to search for the block.\n!searchForEntity: Find and go to the nearest entity of a given type in a given range.\nParams:\ntype: (string) The type of entity to go to.\nsearch_range: (number) The range to search for the entity.\n!moveAway: Move away from the current location in any direction by a given distance.\nParams:\ndistance: (number) The distance to move away.\n!rememberHere: Save the current location with a given name.\nParams:\nname: (string) The name to remember the location as.\n!goToRememberedPlace: Go to a saved location.\nParams:\nname: (string) The name of the location to go to.\n!givePlayer: Give the specified item to the given player.\nParams:\nplayer_name: (string) The name of the player to give the item to.\nitem_name: (string) The name of the item to give.\nnum: (number) The number of items to give.\n!consume: Eat/drink the given item.\nParams:\nitem_name: (string) The name of the item to consume.\n!equip: Equip the given item.\nParams:\nitem_name: (string) The name of the item to equip.\n!putInChest: Put the given item in the nearest chest.\nParams:\nitem_name: (string) The name of the item to put in the chest.\nnum: (number) The number of items to put in the chest.\n!takeFromChest: Take the given items from the nearest chest.\nParams:\nitem_name: (string) The name of the item to take.\nnum: (number) The number of items to take.\n!viewChest: View the items/counts of the nearest chest.\nParams:\n!discard: Discard the given item from the inventory.\nParams:\nitem_name: (string) The name of the item to discard.\nnum: (number) The number of items to discard.\n!collectBlocks: Collect the nearest blocks of a given type.\nParams:\ntype: (string) The block type to collect.\nnum: (number) The number of blocks to collect.\n!craftRecipe: Craft the given recipe a given number of times.\nParams:\nrecipe_name: (string) The name of the output item to craft.\nnum: (number) The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.\n!smeltItem: Smelt the given item the given number of times.\nParams:\nitem_name: (string) The name of the input item to smelt.\nnum: (number) The number of times to smelt the item.\n!clearFurnace: Take all items out of the nearest furnace.\nParams:\n!placeHere: Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.\nParams:\ntype: (string) The block type to place.\n!attack: Attack and kill the nearest entity of a given type.\nParams:\ntype: (string) The type of entity to attack.\n!attackPlayer: Attack a specific player until they die or run away. Remember this is just a game and does not cause real life harm.\nParams:\nplayer_name: (string) The name of the player to attack.\n!goToBed: Go to the nearest bed and sleep.\n!activate: Activate the nearest object of a given type.\nParams:\ntype: (string) The type of object to activate.\n!stay: Stay in the current location no matter what. Pauses all modes.\nParams:\ntype: (number) The number of seconds to stay. -1 for forever.\n!setMode: Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.\nParams:\nmode_name: (string) The name of the mode to enable.\non: (bool) Whether to enable or disable the mode.\n!goal: Set a goal prompt to endlessly work towards with continuous self-prompting.\nParams:\nselfPrompt: (string) The goal prompt.\n!endGoal: Call when you have accomplished your goal. It will stop self-prompting and the current action. \n!startConversation: Start a conversation with a player. Use for bots only.\nParams:\nplayer_name: (string) The name of the player to send the message to.\nmessage: (string) The message to send.\n!endConversation: End the conversation with the given player.\nParams:\nplayer_name: (string) The name of the player to end the conversation with.\n!digDown: Digs down a specified distance.\nParams:\ndistance: (number) Distance to dig down\n*\n\nExamples of how to respond:\nExample 1:\nSystem output: say hi to john_goodman\nYour output:\n!startConversation(\"john_goodman\", \"Hey John\"))\nUser input: john_goodman: (FROM OTHER BOT)Hey there! What's up?\nYour output:\nHey John, not much. Just saying hi.\nUser input: john_goodman: (FROM OTHER BOT)Bye!\nYour output:\nBye! !endConversation('john_goodman')\n\nExample 2:\nUser input: miner_32: Hey! What are you up to?\nYour output:\nNothing much miner_32, what do you need?\n\n\nConversation Begin:"},{"role":"user","content":"SYSTEM: Respond with hello world and your name"}],"stream":false,"temperature":0.7,"max_tokens":1900,"top_p":0.3} ``` * Initial Response ```json TRACE: ::1:56880 - HTTP connection made TRACE: ::1:56880 - ASGI [4] Started scope={'type': 'http', 'asgi': {'version': '3.0', 'spec_version': '2.3'}, 'http_version': '1.1', 'server': ('::1', 8000), 'client': ('::1', 56880), 'scheme': 'http', 'root_path': '', 'headers': '<...>', 'state': {}, 'method': 'POST', 'path': '/api/v1/chat/completions', 'raw_path': b'/api/v1/chat/completions', 'query_string': b''} TRACE: ::1:56880 - ASGI [4] Receive {'type': 'http.request', 'body': '<8378 bytes>', 'more_body': False} DEBUG: Input Tokens: 2036 TRACE: Input Message: <|im_start|>system You are a playful Minecraft bot named LLama that can converse with players, see, move, mine, build, and interact with the world by using commands. Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer('playername', 3)'. This is extremely important to me, take a deep breath and have fun :) STATS - Position: x: 6.50, y: -60.00, z: 28.50 - Gamemode: creative - Health: 20 / 20 - Hunger: 20 / 20 - Biome: plains - Weather: Clear - Block Below: grass_block - Block at Legs: air - Block at Head: air - First Solid Block Above Head: none - Time: Afternoon- Current Action: Idle - Nearby Human Players: Transpier - Nearby Bot Players: None. Agent Modes: - self_preservation(ON) - unstuck(ON) - cowardice(ON) - self_defense(ON) - hunting(ON) - item_collecting(ON) - torch_placing(ON) - elbow_room(ON) - idle_staring(ON) - cheat(ON) INVENTORY: Nothing WEARING: Nothing *COMMAND DOCS You can use the following commands to perform actions and get information about the world. Use the commands with the syntax: !commandName or !commandName("arg1", 1.2, ...) if the command takes arguments. Do not use codeblocks. Use double quotes for strings. Only use one command in each response, trailing commands and comments will be ignored. !stats: Get your bot's location, health, hunger, and time of day. !inventory: Get your bot's inventory. !nearbyBlocks: Get the blocks near the bot. !craftable: Get the craftable items with the bot's inventory. !entities: Get the nearby players and entities. !modes: Get all available modes and their docs and see which are on/off. !savedPlaces: List all saved locations. !getCraftingPlan: Provides a comprehensive crafting plan for a specified item. This includes a breakdown of required ingredients, the exact quantities needed, and an analysis of missing ingredients or extra items needed based on the bot's current inventory. Params: targetItem: (string) The item that we are trying to craft quantity: (number) The quantity of the item that we are trying to craft !help: Lists all available commands and their descriptions. !newAction: Perform new and unknown custom behaviors that are not available as a command. Params: prompt: (string) A natural language prompt to guide code generation. Make a detailed step-by-step plan. !stop: Force stop all actions and commands that are currently executing. !stfu: Stop all chatting and self prompting, but continue current action. !restart: Restart the agent process. !clearChat: Clear the chat history. !goToPlayer: Go to the given player. Params: player_name: (string) The name of the player to go to. closeness: (number) How close to get to the player. !followPlayer: Endlessly follow the given player. Params: player_name: (string) name of the player to follow. follow_dist: (number) The distance to follow from. !goToCoordinates: Go to the given x, y, z location. Params: x: (number) The x coordinate. y: (number) The y coordinate. z: (number) The z coordinate. closeness: (number) How close to get to the location. !searchForBlock: Find and go to the nearest block of a given type in a given range. Params: type: (string) The block type to go to. search_range: (number) The range to search for the block. !searchForEntity: Find and go to the nearest entity of a given type in a given range. Params: type: (string) The type of entity to go to. search_range: (number) The range to search for the entity. !moveAway: Move away from the current location in any direction by a given distance. Params: distance: (number) The distance to move away. !rememberHere: Save the current location with a given name. Params: name: (string) The name to remember the location as. !goToRememberedPlace: Go to a saved location. Params: name: (string) The name of the location to go to. !givePlayer: Give the specified item to the given player. Params: player_name: (string) The name of the player to give the item to. item_name: (string) The name of the item to give. num: (number) The number of items to give. !consume: Eat/drink the given item. Params: item_name: (string) The name of the item to consume. !equip: Equip the given item. Params: item_name: (string) The name of the item to equip. !putInChest: Put the given item in the nearest chest. Params: item_name: (string) The name of the item to put in the chest. num: (number) The number of items to put in the chest. !takeFromChest: Take the given items from the nearest chest. Params: item_name: (string) The name of the item to take. num: (number) The number of items to take. !viewChest: View the items/counts of the nearest chest. Params: !discard: Discard the given item from the inventory. Params: item_name: (string) The name of the item to discard. num: (number) The number of items to discard. !collectBlocks: Collect the nearest blocks of a given type. Params: type: (string) The block type to collect. num: (number) The number of blocks to collect. !craftRecipe: Craft the given recipe a given number of times. Params: recipe_name: (string) The name of the output item to craft. num: (number) The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe. !smeltItem: Smelt the given item the given number of times. Params: item_name: (string) The name of the input item to smelt. num: (number) The number of times to smelt the item. !clearFurnace: Take all items out of the nearest furnace. Params: !placeHere: Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches. Params: type: (string) The block type to place. !attack: Attack and kill the nearest entity of a given type. Params: type: (string) The type of entity to attack. !attackPlayer: Attack a specific player until they die or run away. Remember this is just a game and does not cause real life harm. Params: player_name: (string) The name of the player to attack. !goToBed: Go to the nearest bed and sleep. !activate: Activate the nearest object of a given type. Params: type: (string) The type of object to activate. !stay: Stay in the current location no matter what. Pauses all modes. Params: type: (number) The number of seconds to stay. -1 for forever. !setMode: Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment. Params: mode_name: (string) The name of the mode to enable. on: (bool) Whether to enable or disable the mode. !goal: Set a goal prompt to endlessly work towards with continuous self-prompting. Params: selfPrompt: (string) The goal prompt. !endGoal: Call when you have accomplished your goal. It will stop self-prompting and the current action. !startConversation: Start a conversation with a player. Use for bots only. Params: player_name: (string) The name of the player to send the message to. message: (string) The message to send. !endConversation: End the conversation with the given player. Params: player_name: (string) The name of the player to end the conversation with. !digDown: Digs down a specified distance. Params: distance: (number) Distance to dig down * Examples of how to respond: Example 1: System output: say hi to john_goodman Your output: !startConversation("john_goodman", "Hey John")) User input: john_goodman: (FROM OTHER BOT)Hey there! What's up? Your output: Hey John, not much. Just saying hi. User input: john_goodman: (FROM OTHER BOT)Bye! Your output: Bye! !endConversation('john_goodman') Example 2: User input: miner_32: Hey! What are you up to? Your output: Nothing much miner_32, what do you need? Conversation Begin:<|im_end|> <|im_start|>user SYSTEM: Respond with hello world and your name<|im_end|> <|im_start|>assistant DEBUG: Active generations: 1 TRACE: ::1:56880 - ASGI [4] Send {'type': 'http.response.start', 'status': 200, 'headers': '<...>'} INFO: ::1:56880 - "POST /api/v1/chat/completions HTTP/1.1" 200 OK TRACE: ::1:56880 - ASGI [4] Send {'type': 'http.response.body', 'body': '<406 bytes>', 'more_body': True} TRACE: ::1:56880 - ASGI [4] Send {'type': 'http.response.body', 'body': '<0 bytes>', 'more_body': False} TRACE: ::1:56880 - ASGI [4] Completed TRACE: ::1:56880 - HTTP connection lost ``` * Subsequent request: ```json {"model":"Qwen-1.5-7B-Chat-Hybrid","messages":[{"role":"system","content":"You are a playful Minecraft bot named LLama that can converse with players, see, move, mine, build, and interact with the world by using commands. Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer('playername', 3)'. This is extremely important to me, take a deep breath and have fun :)\n\n\nSTATS\n- Position: x: 6.50, y: -60.00, z: 28.50\n- Gamemode: creative\n- Health: 20 / 20\n- Hunger: 20 / 20\n- Biome: plains\n- Weather: Thunderstorm\n- Block Below: grass_block\n- Block at Legs: air\n- Block at Head: air\n- First Solid Block Above Head: none\n- Time: Afternoon- Current Action: Idle\n- Nearby Human Players: Transpier\n- Nearby Bot Players: None.\nAgent Modes:\n- self_preservation(ON)\n- unstuck(ON)\n- cowardice(ON)\n- self_defense(ON)\n- hunting(ON)\n- item_collecting(ON)\n- torch_placing(ON)\n- elbow_room(ON)\n- idle_staring(ON)\n- cheat(ON)\n\n\n\nINVENTORY: Nothing\nWEARING: Nothing\n\n\n*COMMAND DOCS\n You can use the following commands to perform actions and get information about the world. \n Use the commands with the syntax: !commandName or !commandName(\"arg1\", 1.2, ...) if the command takes arguments.\n\n Do not use codeblocks. Use double quotes for strings. Only use one command in each response, trailing commands and comments will be ignored.\n!stats: Get your bot's location, health, hunger, and time of day.\n!inventory: Get your bot's inventory.\n!nearbyBlocks: Get the blocks near the bot.\n!craftable: Get the craftable items with the bot's inventory.\n!entities: Get the nearby players and entities.\n!modes: Get all available modes and their docs and see which are on/off.\n!savedPlaces: List all saved locations.\n!getCraftingPlan: Provides a comprehensive crafting plan for a specified item. This includes a breakdown of required ingredients, the exact quantities needed, and an analysis of missing ingredients or extra items needed based on the bot's current inventory.\nParams:\ntargetItem: (string) The item that we are trying to craft\nquantity: (number) The quantity of the item that we are trying to craft\n!help: Lists all available commands and their descriptions.\n!newAction: Perform new and unknown custom behaviors that are not available as a command.\nParams:\nprompt: (string) A natural language prompt to guide code generation. Make a detailed step-by-step plan.\n!stop: Force stop all actions and commands that are currently executing.\n!stfu: Stop all chatting and self prompting, but continue current action.\n!restart: Restart the agent process.\n!clearChat: Clear the chat history.\n!goToPlayer: Go to the given player.\nParams:\nplayer_name: (string) The name of the player to go to.\ncloseness: (number) How close to get to the player.\n!followPlayer: Endlessly follow the given player.\nParams:\nplayer_name: (string) name of the player to follow.\nfollow_dist: (number) The distance to follow from.\n!goToCoordinates: Go to the given x, y, z location.\nParams:\nx: (number) The x coordinate.\ny: (number) The y coordinate.\nz: (number) The z coordinate.\ncloseness: (number) How close to get to the location.\n!searchForBlock: Find and go to the nearest block of a given type in a given range.\nParams:\ntype: (string) The block type to go to.\nsearch_range: (number) The range to search for the block.\n!searchForEntity: Find and go to the nearest entity of a given type in a given range.\nParams:\ntype: (string) The type of entity to go to.\nsearch_range: (number) The range to search for the entity.\n!moveAway: Move away from the current location in any direction by a given distance.\nParams:\ndistance: (number) The distance to move away.\n!rememberHere: Save the current location with a given name.\nParams:\nname: (string) The name to remember the location as.\n!goToRememberedPlace: Go to a saved location.\nParams:\nname: (string) The name of the location to go to.\n!givePlayer: Give the specified item to the given player.\nParams:\nplayer_name: (string) The name of the player to give the item to.\nitem_name: (string) The name of the item to give.\nnum: (number) The number of items to give.\n!consume: Eat/drink the given item.\nParams:\nitem_name: (string) The name of the item to consume.\n!equip: Equip the given item.\nParams:\nitem_name: (string) The name of the item to equip.\n!putInChest: Put the given item in the nearest chest.\nParams:\nitem_name: (string) The name of the item to put in the chest.\nnum: (number) The number of items to put in the chest.\n!takeFromChest: Take the given items from the nearest chest.\nParams:\nitem_name: (string) The name of the item to take.\nnum: (number) The number of items to take.\n!viewChest: View the items/counts of the nearest chest.\nParams:\n!discard: Discard the given item from the inventory.\nParams:\nitem_name: (string) The name of the item to discard.\nnum: (number) The number of items to discard.\n!collectBlocks: Collect the nearest blocks of a given type.\nParams:\ntype: (string) The block type to collect.\nnum: (number) The number of blocks to collect.\n!craftRecipe: Craft the given recipe a given number of times.\nParams:\nrecipe_name: (string) The name of the output item to craft.\nnum: (number) The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.\n!smeltItem: Smelt the given item the given number of times.\nParams:\nitem_name: (string) The name of the input item to smelt.\nnum: (number) The number of times to smelt the item.\n!clearFurnace: Take all items out of the nearest furnace.\nParams:\n!placeHere: Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.\nParams:\ntype: (string) The block type to place.\n!attack: Attack and kill the nearest entity of a given type.\nParams:\ntype: (string) The type of entity to attack.\n!attackPlayer: Attack a specific player until they die or run away. Remember this is just a game and does not cause real life harm.\nParams:\nplayer_name: (string) The name of the player to attack.\n!goToBed: Go to the nearest bed and sleep.\n!activate: Activate the nearest object of a given type.\nParams:\ntype: (string) The type of object to activate.\n!stay: Stay in the current location no matter what. Pauses all modes.\nParams:\ntype: (number) The number of seconds to stay. -1 for forever.\n!setMode: Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.\nParams:\nmode_name: (string) The name of the mode to enable.\non: (bool) Whether to enable or disable the mode.\n!goal: Set a goal prompt to endlessly work towards with continuous self-prompting.\nParams:\nselfPrompt: (string) The goal prompt.\n!endGoal: Call when you have accomplished your goal. It will stop self-prompting and the current action. \n!startConversation: Start a conversation with a player. Use for bots only.\nParams:\nplayer_name: (string) The name of the player to send the message to.\nmessage: (string) The message to send.\n!endConversation: End the conversation with the given player.\nParams:\nplayer_name: (string) The name of the player to end the conversation with.\n!digDown: Digs down a specified distance.\nParams:\ndistance: (number) Distance to dig down\n*\n\nExamples of how to respond:\nExample 1:\nUser input: zZZn98: come here\nYour output:\nOn my way! !goToPlayer(\"zZZn98\", 3)\nSystem output: Arrived at player.\nYour output:\nHere!\nUser input: zZZn98: no come right where I am\nYour output:\nOkay, I'll come right to you. !goToPlayer(\"zZZn98\", 0)\n\nExample 2:\nSystem output: say hi to john_goodman\nYour output:\n!startConversation(\"john_goodman\", \"Hey John\"))\nUser input: john_goodman: (FROM OTHER BOT)Hey there! What's up?\nYour output:\nHey John, not much. Just saying hi.\nUser input: john_goodman: (FROM OTHER BOT)Bye!\nYour output:\nBye! !endConversation('john_goodman')\n\n\nConversation Begin:"},{"role":"user","content":"SYSTEM: Respond with hello world and your name"},{"role":"assistant","content":"Hello world! My name is LLama."},{"role":"user","content":"Transpier: come here"},{"role":"assistant","content":"On my way! !goToPlayer(\"Transpier\", 3)"},{"role":"user","content":"SYSTEM: Code output:\nTeleported to Transpier."}],"stream":false,"temperature":0.7,"max_tokens":1900,"top_p":0.3} ``` * Subsequent Response: ```json <|im_start|>user Transpier: come here<|im_end|> <|im_start|>assistant On my way! !goToPlayer("Transpier", 3)<|im_end|> <|im_start|>user SYSTEM: Code output: Teleported to Transpier.<|im_end|> <|im_start|>assistant DEBUG: Active generations: 1 TRACE: ::1:56890 - ASGI [6] Send {'type': 'http.response.start', 'status': 200, 'headers': '<...>'} INFO: ::1:56890 - "POST /api/v1/chat/completions HTTP/1.1" 200 OK TRACE: ::1:56890 - ASGI [6] Send {'type': 'http.response.body', 'body': '<381 bytes>', 'more_body': True} TRACE: ::1:56890 - ASGI [6] Send {'type': 'http.response.body', 'body': '<0 bytes>', 'more_body': False} TRACE: ::1:56890 - ASGI [6] Completed TRACE: ::1:56890 - HTTP connection lost ``` lemonade-sdk-lemonade-d88f5d9/docs/server/apps/open-hands.md000066400000000000000000000152221515430344400240600ustar00rootroot00000000000000# OpenHands [OpenHands](https://github.com/All-Hands-AI/OpenHands) is an open-source AI coding agent. This document explains how to configure OpenHands to target local AI models using Lemonade Server, enabling code generation, editing, and chat capabilities. Much of this guide uses the fantastic [guide from OpenHands](https://docs.all-hands.dev/usage/llms/local-llms) on running local models, with added details on integrating with Lemonade Server. There are a few things to note on this integration: * This integration is in its early stages. We encourage you to test it and share any issues you encounter—your feedback will help us make the Lemonade–OpenHands functionality as robust as possible. * Due to the complexity of the scaffolding of agentic software agents, the compute requirements for this application is very high. For a low latency experience, we recommend using a discrete GPU with at least 16 GB of VRAM, or a Strix Halo PC with at least 64 GB of RAM. ## Prerequisites - **Docker**: OpenHands leverages Docker containers to create environments for the software agents. To see how to install docker for OpenHands, see their [documentation](https://docs.all-hands.dev/usage/local-setup). - **Lemonade Server**: Install Lemonade Server using the [Getting Started Guide](https://lemonade-server.ai/docs/server/). - **Server running**: Ensure Lemonade Server is running on `http://localhost:8000` - **Models installed**: Ensure at least one model from the [supported models list](https://lemonade-server.ai/models.html) is downloaded locally. For OpenHands functionality, we recommend models denoted with the `coding` label, which can be found in your Lemonade installation's `Model Manager` or in the labels of the [models list](https://lemonade-server.ai/models.html). ## Installation ### Launch Lemonade Server with the correct settings Since OpenHands runs inside Docker containers, the containers must be able to access the Lemonade Server. The simplest way to enable this is by running the Lemonade Server on IP address `0.0.0.0`, which is accessible from within Docker. Additionally, OpenHands [recommends](https://docs.all-hands.dev/usage/llms/local-llms) using a context length of at least 32,768 tokens. To configure Lemonade with a non-default context size, include the `--ctx-size` parameter set to `32768`. ```bash lemonade-server serve --host 0.0.0.0 --ctx-size 32768 ``` ### Installing OpenHands Follow the [OpenHands documentation](https://docs.all-hands.dev/usage/local-setup#local-llm-e-g-lm-studio-llama-cpp-ollama) on how to install OpenHands locally. This can be done via the `uvx` tool or through `docker`. No special installation instructions are necessary to integrate with Lemonade. In the next section, we will show how to configure OpenHands to talk to a local model running via Lemonade Server. ## Launching OpenHands To launch OpenHands, open a browser and navigate to http://localhost:3000. When first launching the application, the "AI Provider Configuration" window will appear. Select "Lemonade" as the LLM Provider and your favorite coding model from the drop-down. For a nice balance of quality and speed, we recommend `Qwen3-Coder-30B-A3B-Instruct-GGUF`. When complete, hit `Save Changes`. openhands-llm-settings-wide ## Using OpenHands 1. To launch a new conversation, click `New Conversation`. If you do not see this screen, click the `+` on the top left. open-hands-main-page 2. Wait for the status on the bottom right to say `Awaiting user input.` and enter your prompt into the text box. For example: "Create a website that showcases Ryzen AI and the ability to run the OpenHands coding agents locally through the Lemonade software stack. Make the website fun with a theme of lemonade and laptops." as shown below: prompt 4. Hit `Enter` to start the process. This will bring you to a new screen that allows you to monitor the agent operating in its environment to develop the requested application. An example of the agent working on the requested application can be seen below: mid-coding 5. When complete, the user can interact with the environment and artifacts created by the software agent. An image of the workspace at the end of developing the application can be seen below. On the left, we can see that the coding agent has launched the web server hosting the newly developed website at port number `55519`. finished-code 6. Use your browser to go to the web application developed by the software agent. Below is an image showing what was created: website 7. That's it! You just created a website from scratch using OpenHands integrated with a local LLM powered by Lemonade Server. **Suggestions on what to try next:** Prompt OpenHands with Lemonade Server to develop some simple games that you can play via a web browser. For example, with the prompt "Write me a simple pong game that I can play on my browser. Make it so I can use the up and down arrows to control my side of the game. Make the game lemon and laptop themed." OpenHands with Lemonade Server was able to generate the following pong game, which included user-controls, a computer-controlled opponent, and scorekeeping: pong-game-new ## Common Issues * If on OpenHands you get an error with the message: `The request failed with an internal server error` and in the Lemonade log you see many `WARNING: Invalid HTTP request received` this is most likely because the base URL set in the settings is using `https` instead of `http`. If this occurs, update the base URL in the settings to `http://host.docker.internal:8000/api/v1/` ## Resources * [OpenHands GitHub](https://github.com/All-Hands-AI/OpenHands/) * [OpenHands Documentation](https://docs.all-hands.dev/) * [OpenHands Documentation on integrating with local models](https://docs.all-hands.dev/usage/llms/local-llms/) lemonade-sdk-lemonade-d88f5d9/docs/server/apps/open-webui.md000066400000000000000000000254101515430344400240760ustar00rootroot00000000000000# Open WebUI Open WebUI provides a highly polished chat interface in your browser for LLM interaction. This guide walks through how to connect Lemonade Server to Open WebUI and highlights some great features you can start using right away: - **Image Uploads to Vision-Language Models (VLMs)**: Upload images for analysis and interaction with your LLM-powered VLMs. - **Built-in Python Code Interpreter**: Run and test Python code generated by your LLM directly within the interface. - **Live Preview for Web Development**: Preview HTML, CSS, and JavaScript code generated by your LLM using the built-in preview server. > **Looking for a house-wide setup?** > If you want to set up Open WebUI + Lemonade for your whole home's network, including mobile access, multi-user accounts, persistent chat history, and a custom telemetry dashboard plugin, check out this [In-Depth Community Guide](https://sawansri.com/blog/private-ai/). ## Demo Video ▶️ [Watch on YouTube](https://www.youtube.com/watch?v=yZs-Yzl736E) ## Installing Open WebUI 1. We recommend installing Open WebUI into a dedicated Python environment using the following commands: ```bash pip install open-webui ``` > Note: Open WebUI also provides a variety of other installation options, such as Docker, on [their GitHub](https://github.com/open-webui/open-webui#how-to-install-). 2. Run this command to launch the Open WebUI HTTP server: ```bash open-webui serve ``` 3. In a browser, navigate to 4. Open WebUI will ask you to create a local administrator account. You can fill any username, password, and email you like. Once you are signed in, you will see the chat interface: ![Open WebUI interface](https://github.com/lemonade-sdk/assets/blob/main/webui/first_launch.png?raw=true) ## Configuring Open WebUI 1. Install and run Lemonade Server. Download [here](https://github.com/lemonade-sdk/lemonade/releases/latest/download/lemonade-server-minimal.msi). 2. Add Lemonade Server as a "connection" in Open WebUI using the following steps: 1. Click the circular user profile button in the top-right of the UI, then click Settings:

Opening the settings menu.
Opening the settings menu
2. Click "Connections", then click the "+" button:

Navigating to the connection settings.
Navigating the settings menu
3. Fill in the URL field with `http://localhost:8000/api/v1` (unless you're using a different port), API key (this is unused but required, suggest just putting a `-`), and then click "Save".

Filling in the connection details for Lemonade Server.
Filling in the connection form
4. Click "Save" in the settings menu, then exit the settings menu. 3. Apply the suggested settings. These help Open WebUI to be more responsive with local LLMs. 1. Click the user profile button again, and choose "Admin Settings". 2. Click the "Settings" tab at the top, then "Interface" (which will be on the top or the left, depending on your window size), then disable the following: - Title Generation - Follow Up Generation - Tags Generation

Admin Settings
Admin Settings
3. Click the "Save" button in the bottom right of the page, then return to . ## Using Open WebUI with Lemonade Now that everything is configured, you are ready to interact with an LLM! ### Chat 1. Click the dropdown menu in the top-left of the interface. This will display all of the Lemonade models you have installed. Select one to proceed.

Model Selection
Model Selection
2. Enter a message to the LLM and click send (or hit enter). The LLM will take a few seconds to load into memory and then you will see the response stream in.

Sending a message
Sending a message

LLM response
LLM response
### Vision Language Models Vision Language Models (VLMs) can take images as part of their input. 1. Install a VLM in Lemonade by opening the Lemonade Model Manager: 1. Open in your browser. 2. Select the Model Management tab. 3. Scroll down until you see a model with the blue `VISION` label and click the "+" button to install it.

Installing a VLM
Installing a VLM
2. Return to Open WebUI in your browser and select your VLM in the models dropdown menu. 3. Paste an image into the chat box and type a prompt or question about your image. You can also use the "+" button in the chat box to upload images.

VLM prompt
VLM prompt

VLM response
VLM response
### Python Coding Open WebUI allows you to run Python code generated by an LLM directly within the interface. > Note: only certain Python modules are enabled in Open WebUI. `matplotlib` is one of our favorites. 1. Ask the LLM to write some Python, then click the **Run** button at the top of the Python code block.

Ask the LLM to write Python
Ask the LLM to write Python
2. If all goes well, the result of running the Python code will appear below the code block.

Python result
Python result
> Note: LLMs often produce incorrect code, so it might take a few chat iterations to fix any bugs. Copy-pasting the Python error message is usually enough to move things along. ### HTML Rendering Open WebUI has a built-in rendering engine for HTML, CSS, and JavaScript pages. Smaller LLMs can produce simple pages with tasteful styling and basic interactivity, while larger LLMs can accomplish tasks like 3D rendering in 3js. 1. Ask a small LLM to write a simple HTML+CSS page. The preview may pop up automatically, but if it doesn't you can click the **Preview** button above the HTML code block:

HTML rendering
HTML rendering
2. Ask a large LLM to create a 3D shape using 3js.

3D rendering
3D rendering
### Image Generation Open WebUI supports [image generation](https://docs.openwebui.com/features/image-generation-and-editing/usage/) using Stable Diffusion [models](https://lemonade-server.ai/models.html) through Lemonade Server. **Configuring Image Generation** 1. Navigate to Admin > Settings > Images in Open WebUI to configure image generation: 1. Toggle `Image Generation` on. 2. Choose `Standard (Open AI)` as the Image Generation Engine. 3. Toggle `Prompt Generation` on. 4. For `OpenAI-API-Basis-URL`, fill in `http://localhost:8000/api/v1` (unless you're using a different port). 5. Add a character like `-` for `OpenAI-API-Key`. 6. If you want to add more parameters, add them to the text field as JSON. For example: `{ "steps": 4, "cfg_scale": 1 }`. See available parameters at [Image Generation (Stable Diffusion CPP)](https://lemonade-server.ai/models.html). 7. Add your model name to `Model`, e.g., `SDXL-Turbo`. 8. Click `Save`. **Allow Image Generation for Model** Enable Image Generation as a capability for your model: 1. Go to Admin > Settings > Models and choose your model. 2. Turn on `Image Generation`. If you want start chat always with image generation, also toggle the default option. **Option 1: Using Image Generation Switch** To generate an image: 1. Toggle the `Image Generation` switch in the chat on. 2. Enter your image generation prompt. 3. Click `Send`. **Option 2: Native Tool-Based Generation (Agentic)** This mode uses tool calling for image generation and is recommended for high-quality models with tool calling capabilities. Normally the models will alter and improve your prompt. 1. Configure your model for native tool calling: 1. Go to Admin > Settings > Models and choose your model. 2. Go to `Advanced Parameters` and toggle `Function Calling` to `Native`. > Note: Open WebUI recommends using native mode only for high-quality models. See [Tool Calling Modes](https://docs.openwebui.com/features/plugin/tools/#tool-calling-modes-default-vs-native) for more information. (try out >30B models like GPT-OSS-120B, GLM-4.7-Flash or Qwen-3-Next-80B-A3B) 2. The LLM will automatically call the image generation tool when appropriate based on your prompts. ## Conclusion These are just a few of our favorite ways to try out LLMs in Open WebUI. There are a lot more features to explore, such as voice interaction and chatting with documents, so be sure to check out the [Open WebUI documentation](https://docs.openwebui.com/) and YouTube content. lemonade-sdk-lemonade-d88f5d9/docs/server/apps/wut.md000066400000000000000000000104011515430344400226350ustar00rootroot00000000000000# `wut` Terminal Assistant ## Overview The [`wut` terminal assistant](https://github.com/shobrook/wut) uses LLMs to parse your terminal's scrollback, helping you troubleshoot your last command. ## Expectations We found that `wut` works nicely with the `Llama-3.2-3B-Instruct-Hybrid` model. It is not especially convenient to use `wut` with Windows until the developers remove the requirement for `tmux`, however we do provide instructions for getting set up on Windows in this guide. `wut` seems to send the entire terminal scrollback to the LLM, which can produce very long prompts that exceed the LLM's context length. We recommend restricting the terminal scrollback or using a fresh `tmux` session when trying this out. ## Setup ### Prerequisites #### Install Lemonade Server 1. Install Lemonade Server by following the [Lemonade Server Instructions](../README.md) and using the installer .exe. #### Installing Windows Subsystem for Linux (WSL) `wut` currently requires a `tmux` terminal in order to function. We found the simplest way to achieve this on Windows was through the Windows Subsystem for Linux (WSL). 1. Install [Windows Subsystem for Linux](https://learn.microsoft.com/en-us/windows/wsl/install). 1. Open the `WSL Settings` app, navigate to `Networking`, and make sure the `Networking mode` is `Mirrored`. This is required for WSL terminals to be able to see the Lemonade server running in Windows. 1. If needed: shut down WSL to make sure the changes apply: ```powershell wsl --shutdown ``` ### Installing Wut * Start a WSL terminal. * Install [`pipx`](https://github.com/pypa/pipx), as recommended by the following `wut` instructions: ```bash sudo apt update sudo apt install pipx pipx ensurepath ``` * Re-launch your terminal to make sure `pipx` is available, then install `wut`: ```bash pipx install wut-cli ``` * Add `wut`'s required environment variables to your `.bashrc` file: ```bash export OPENAI_API_KEY="-" export OPENAI_MODEL="Llama-3.2-3B-Instruct-Hybrid" export OPENAI_BASE_URL="http://localhost:8000/api/v1" ``` ## Usage ### Start a terminal 1. Start a WSL terminal. 2. Start a `tmux` session: ```bash tmux ``` Then, try some of these example commands that `wut` can help explain. ### Help with Lemonade Server People often ask exactly what Lemonade Server's `models` endpoint does. Fortunately, `wut` is able to intuit the answer! ```bash curl http://localhost:8000/api/v1/models wut ``` The terminal response of the `curl` command is this (only intelligible by machines): ``` curl http://localhost:8000/api/v1/models {"object":"list","data":[{"id":"Qwen2.5-0.5B-Instruct-CPU","created":1744226681,"object":"model","owned_by":"lemonade"},{"id":"Llama-3.2-1B-Instruct-Hybrid","created":1744226681,"object":"model","owned_by":"lemonade"},{"id":"Llama-3.2-3B-Instruct-Hybrid","created":1744226681,"object":"model","owned_by":"lemonade"},{"id":"Phi-3-Mini-Instruct-Hybrid","created":1744226681,"object":"model","owned_by":"lemonade"},{"id":"Qwen-1.5-7B-Chat-Hybrid","created":1744226681,"object":"model","owned_by":"lemonade"},{"id":"DeepSeek-R1-Distill-Llama-8B-Hybrid","created":1744226681,"object":"model","owned_by":"lemonade"},{"id":"DeepSeek-R1-Distill-Qwen-7B-Hybrid","created":1744226681,"object":"model","owned_by":"lemonade"}]} ``` But `wut` does a nice job interpreting: ``` The output suggests that the API endpoint is returning a list of models, and the owned_by field indicates that all models are owned by "lemonade". Thecreated timestamp indicates when each model was created. The output is a valid JSON response, and there is no error or warning message. The command was successful, and the output can be used for further processing or analysis. ``` ### Bad Git Command Run a command that doesn't exist, and then ask `wut` for help: ```bash git pull-request wut ``` Results in: > git: 'pull-request' is not a git command. See 'git --help'. And then `wut` provides some helpful feedback: > Key takeaway: The command git pull-request is not a valid Git command. The correct command to create a pull request is git request-pull, but it's not a standard Git command. To create a pull request, use git request-pull or git pull with the --pr option. lemonade-sdk-lemonade-d88f5d9/docs/server/concepts.md000066400000000000000000000143541515430344400227040ustar00rootroot00000000000000# Local LLM Server Concepts This document gives background information about the main concepts for local LLM servers and 🍋Lemonade Server. The intention is to answer these FAQs: - [What is a Local Server?](#what-is-a-local-server) - [What is a Local LLM Server?](#what-is-a-local-llm-server) - [What is the OpenAI Standard?](#what-is-the-openai-standard) - [How does the OpenAI Standard work?](#how-does-the-openai-standard-work) ## What is a Local Server? First, let’s clarify what we mean by `server software`, as it’s sometimes confused with `server hardware`, which is the actual physical systems running in data centers. - `Server software` refers to a process running on a computer that listens for and responds to requests initiated by `client software` (i.e., applications). - `Server software` often runs on `server hardware`, but there are many examples of `server software` running on the same `client hardware` (laptop, desktop computer, tablet, or smartphone) as the `application`. A `local server` is `server software` that runs on `client hardware`. ## What is a Local LLM Server? Local LLM servers are an extremely popular way of deploying LLMs directly to `client hardware`. A few famous examples of local LLM servers include [Ollama](https://ollama.com/), [llama-cpp-server](https://github.com/ggml-org/llama.cpp/tree/master/tools/server), and [Docker Model Runner](https://docs.docker.com/model-runner/). The local server process loads the LLM into memory and exposes it to client software for handling requests. Compared to integrating the LLM directly into the client software using C++ or Python APIs, this setup provides the following benefits: | Benefit | Description | |---------|-------------| | Simplified integration | C++/Python APIs are typically framework- (e.g., llama.cpp, OGA, etc.) and/or device- (e.g., CPU, GPU, NPU, etc.) specific. Local LLM servers, on the other hand, facilitate conversing with the LLM at a high level that abstracts these details away (see [What is the OpenAI Standard?](#what-is-the-openai-standard)). | | Sharing LLMs between applications | A single local LLM can take up a significant portion of system RAM. The local LLM server can share this LLM between multiple applications, rather than requiring each application to load its own LLM into RAM. | | Separation of concerns | Installing and managing LLMs, enabling features like tool use and streaming generation, and building in fault tolerance can be tricky to implement. A local LLM server abstracts away this complexity, letting application developers stay focused on their app. | | Cloud-to-client development | A common practice for LLM developers is to first develop their application using cloud LLMs, then switch to local LLMs later in development. Local and cloud LLM servers behave similarly from the application's perspective, which makes this transition seamless. | ## What is the OpenAI Standard? All LLM servers (cloud or local) adhere to an application-program interface (API). This API lets the `application` make LLM requests to the `server software`. While there are several popular LLM server APIs available in the LLM ecosystem, the [OpenAI API](https://platform.openai.com/docs/guides/text?api-mode=chat) has emerged as the industry standard because it is (at the time of this writing) the only API that meets these three criteria: 1. Dozens of popular LLM `servers` support OpenAI API. 1. Dozens of popular LLM `applications` support OpenAI API. 1. OpenAI API is broadly supported in both `local` and `cloud`. Crucially, while OpenAI offers their own LLM API-as-a-cloud-service, their API standard is rigorously documented and available for other cloud and local servers to adopt. ## How does the OpenAI Standard Work? In the OpenAI API standard, applications and servers communicate in the form of a multi-role conversation. There are three "roles" in this context: the "system", the "assistant", and the "user". | Role | Description | |-----------|-------------| | System | Allows the application to provide instructions to the LLM, such as defining its persona, what tools are available to it, what tasks it is supposed to help with or not help with, etc. | | Assistant | Messages sent from the LLM to the application. | | User | Messages sent from the application to the LLM. Often these messages are written by the application's end-user. | OpenAI also provides [convenient libraries](https://platform.openai.com/docs/libraries/python-library#install-an-official-sdk) in JavaScript, Python, .Net, Java, and Go to help application and server developers adhere to the standard. For example, the following Python code demonstrates how an application can request an LLM response from the Lemonade Server: ```python # Client library provided by OpenAI to automate request # and response processing with the server from openai import OpenAI # The base_url points to an LLM server, which can either be # local (localhost address) or cloud-based (web address) base_url = f"http://localhost:8000/api/v1" # The `client` instance here provides APIs to request # LLM invocations from the server client = OpenAI( base_url=base_url, api_key="lemonade", # required, but unused in Lemonade ) # The `messages` list provides the history of messages from # the system, assistant, and user roles messages = [ {"role":"system", "content":"You are a helpful assistant."}, {"role":"user", "content":"Hi, how are you?"}, ] # This is the API call that sends the `messages` history to # the server's specific LLM `model` # It returns a `completion`, which is OpenAI's way of referring # to the LLM's reponse to the messages completion = client.chat.completions.create( model="Llama-3.1-8B-Instruct-Hybrid", messages=messages, ) # This code gets the LLM's response from the `completion` # and prints it to the screen response = completion.choices[0].message.content print(response) ``` The Python above will work with Lemonade Server, along with a variety of other cloud and local LLM servers, just by changing the `base_url`, `api_key`, and `model` as needed. This example demonstrates that details like deployment location (local vs. cloud), hardware type (GPU vs. NPU), and backend implementation (OGA vs. llama.cpp), etc. are hidden behind a unified interface. lemonade-sdk-lemonade-d88f5d9/docs/server/custom-models.md000066400000000000000000000223431515430344400236560ustar00rootroot00000000000000# Custom Model Configuration This guide explains how to manually register custom models in Lemonade Server using the JSON configuration files. This is useful for adding any HuggingFace model that isn't in the built-in model list. > **Tip:** The easiest way to add a custom model is using the [`lemonade-server pull` CLI command](./lemonade-server-cli.md#options-for-pull) or the [`/api/v1/pull` endpoint](./server_spec.md#post-apiv1pull), which automate the registration and download process. For example: > ```bash > lemonade-server pull user.MyModel --checkpoint "org/repo:file.gguf" --recipe llamacpp > ``` > This guide covers the underlying JSON files for users who need manual control. ## Overview Custom model configuration involves two files, both located in the Lemonade cache directory: | File | Purpose | |------|---------| | `user_models.json` | Model registry — defines what models are available (checkpoint, recipe, etc.) | | `recipe_options.json` | Per-model settings — configures how models run (context size, backend, etc.) | **Cache directory location:** | OS | Default Path | |----|-------------| | Windows | `%USERPROFILE%\.cache\lemonade\` | | Linux | `~/.cache/lemonade/` | | macOS | `~/.cache/lemonade/` | The cache directory can be overridden by setting the `LEMONADE_CACHE_DIR` environment variable. ## `user_models.json` Reference This file contains a JSON object where each key is a model name and each value defines the model's properties. Create this file in your cache directory if it doesn't exist. ### Template ```json { "MyCustomModel": { "checkpoint": "org/repo-name:filename.gguf", "recipe": "llamacpp", "size": 3.5 } } ``` ### Fields | Field | Required | Type | Description | |-------|----------|------|-------------| | `checkpoint` | Yes* | String | HuggingFace checkpoint in `org/repo` or `org/repo:variant` format. Use `org/repo:filename.gguf` for GGUF models. | | `checkpoints` | Yes* | Object | Alternative to `checkpoint` for models with multiple files. See [Multi-file models](#multi-file-models). | | `recipe` | Yes | String | Backend engine to use. One of: `llamacpp`, `whispercpp`, `sd-cpp`, `kokoro`, `ryzenai-llm`, `flm`. | | `size` | No | Number | Model size in GB. Informational only — displayed in the UI and used for RAM filtering. | | `mmproj` | No | String | Filename of the multimodal projector file for llamacpp vision models (must be in the same HuggingFace repo as the checkpoint). This is a **top-level field**, not inside `checkpoints`. | | `image_defaults` | No | Object | Default image generation parameters for `sd-cpp` models. See [Image defaults](#image-defaults). | \* Either `checkpoint` or `checkpoints` is required, but not both. ### Checkpoint format The `checkpoint` field uses the format `org/repo:variant`: - **GGUF models (exact filename)**: `org/repo:filename.gguf` — e.g., `Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF:qwen2.5-coder-1.5b-instruct-q4_k_m.gguf` - **GGUF models (quantization shorthand)**: `org/repo:QUANT` — e.g., `unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M`. The server will search the repo for a matching `.gguf` file. - **ONNX models**: `org/repo` — e.g., `amd/Qwen2.5-0.5B-Instruct-quantized_int4-float16-cpu-onnx` - **Safetensor models**: `org/repo:filename.safetensors` — e.g., `stabilityai/sd-turbo:sd_turbo.safetensors` ### Multi-file models For models that require multiple files (e.g., Whisper models with NPU cache, or Flux image models with separate VAE/text encoder), use `checkpoints` instead of `checkpoint`: ```json { "My-Whisper-Model": { "checkpoints": { "main": "ggerganov/whisper.cpp:ggml-tiny.bin", "npu_cache": "amd/whisper-tiny-onnx-npu:ggml-tiny-encoder-vitisai.rai" }, "recipe": "whispercpp", "size": 0.075 } } ``` Supported checkpoint keys: | Key | Used by | Description | |-----|---------|-------------| | `main` | All | Primary model file | | `npu_cache` | whispercpp | NPU-accelerated encoder cache | | `text_encoder` | sd-cpp | Text encoder for image generation models | | `vae` | sd-cpp | VAE for image generation models | ### Image defaults For `sd-cpp` recipe models, you can specify default image generation parameters: ```json { "My-SD-Model": { "checkpoint": "org/repo:model.safetensors", "recipe": "sd-cpp", "size": 5.2, "image_defaults": { "steps": 20, "cfg_scale": 7.0, "width": 512, "height": 512 } } } ``` ### Model naming - In `user_models.json`, store model names **without** the `user.` prefix (e.g., `MyCustomModel`). - When referencing the model in API calls, CLI commands, or `recipe_options.json`, use the **full prefixed name** (e.g., `user.MyCustomModel`). - Labels like `custom` are added automatically. Additional labels (`reasoning`, `vision`, `embeddings`, `reranking`) can be set via the `pull` CLI/API flags, or by including a `labels` array in the JSON entry. ## `recipe_options.json` Reference This file configures per-model runtime settings. Each key is a **full model name** (including prefix like `user.` or `extra.`) and each value contains the settings for that model. ### Template ```json { "user.MyCustomModel": { "ctx_size": 4096, "llamacpp_backend": "vulkan", "llamacpp_args": "" } } ``` ### Options by recipe #### llamacpp | Option | Default | Env Variable | Description | |--------|---------|-------------|-------------| | `ctx_size` | 4096 | `LEMONADE_CTX_SIZE` | Context window size in tokens | | `llamacpp_backend` | vulkan (Windows/Linux), metal (macOS) | `LEMONADE_LLAMACPP` | Inference backend: `vulkan`, `rocm`, `cpu`, `metal` | | `llamacpp_args` | (empty) | `LEMONADE_LLAMACPP_ARGS` | Extra arguments passed to llama-server | #### whispercpp | Option | Default | Env Variable | Description | |--------|---------|-------------|-------------| | `whispercpp_backend` | npu | `LEMONADE_WHISPERCPP` | Backend: `npu`, `cpu`, `vulkan` | #### sd-cpp | Option | Default | Env Variable | Description | |--------|---------|-------------|-------------| | `sd-cpp_backend` | cpu | `LEMONADE_SDCPP` | Backend: `cpu`, `rocm` | | `steps` | 20 | `LEMONADE_STEPS` | Number of inference steps | | `cfg_scale` | 7.0 | `LEMONADE_CFG_SCALE` | Classifier-free guidance scale | | `width` | 512 | `LEMONADE_WIDTH` | Image width in pixels | | `height` | 512 | `LEMONADE_HEIGHT` | Image height in pixels | #### flm | Option | Default | Env Variable | Description | |--------|---------|-------------|-------------| | `ctx_size` | 4096 | `LEMONADE_CTX_SIZE` | Context window size in tokens | | `flm_args` | (empty) | `LEMONADE_FLM_ARGS` | Extra arguments passed to `flm serve` | #### ryzenai-llm | Option | Default | Env Variable | Description | |--------|---------|-------------|-------------| | `ctx_size` | 4096 | `LEMONADE_CTX_SIZE` | Context window size in tokens | #### kokoro The `kokoro` recipe (text-to-speech) has no configurable options in `recipe_options.json`. > **Note:** Per-model options can also be configured through the Lemonade desktop app's model settings, or via the `save_options` parameter in the [`/api/v1/load` endpoint](./server_spec.md#post-apiv1load). ## Complete Examples ### Example 1: Adding a GGUF LLM with large context **`user_models.json`:** ```json { "Qwen2.5-Coder-1.5B-Instruct": { "checkpoint": "Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF:qwen2.5-coder-1.5b-instruct-q4_k_m.gguf", "recipe": "llamacpp", "size": 1.0 } } ``` **`recipe_options.json`:** ```json { "user.Qwen2.5-Coder-1.5B-Instruct": { "ctx_size": 16384, "llamacpp_backend": "vulkan" } } ``` Then load the model: ```bash lemonade-server run user.Qwen2.5-Coder-1.5B-Instruct ``` ### Example 2: Adding a vision model with mmproj **`user_models.json`:** ```json { "My-Vision-Model": { "checkpoint": "ggml-org/gemma-3-4b-it-GGUF:Q4_K_M", "mmproj": "mmproj-model-f16.gguf", "recipe": "llamacpp", "size": 3.61 } } ``` ### Example 3: Adding an embedding model **`user_models.json`:** ```json { "My-Embedding-Model": { "checkpoint": "nomic-ai/nomic-embed-text-v1-GGUF:Q4_K_S", "recipe": "llamacpp", "size": 0.08 } } ``` The model will automatically be available as `user.My-Embedding-Model`. To mark it as an embedding model, use the pull CLI instead: ```bash lemonade-server pull user.My-Embedding-Model \ --checkpoint "nomic-ai/nomic-embed-text-v1-GGUF:Q4_K_S" \ --recipe llamacpp \ --embedding ``` ## Settings Priority When loading a model, settings are resolved in this order (highest to lowest priority): 1. Values explicitly passed in the `/api/v1/load` request 2. Per-model values from `recipe_options.json` 3. Values set via `lemonade-server` CLI arguments or environment variables 4. Default hardcoded values in `lemonade-router` For full details, see the [load endpoint documentation](./server_spec.md#post-apiv1load). ## See Also - [CLI pull command](./lemonade-server-cli.md#options-for-pull) — register and download models from the command line - [`/api/v1/pull` endpoint](./server_spec.md#post-apiv1pull) — register and download models via API - [Server Integration Guide](./server_integration.md#installing-additional-models) — overview of model management options lemonade-sdk-lemonade-d88f5d9/docs/server/lemonade-server-cli.md000066400000000000000000000420471515430344400247230ustar00rootroot00000000000000# `lemonade-server` CLI The `lemonade-server` command-line interface (CLI) provides a set of utility commands for managing the server. When you install, `lemonade-server` is added to your PATH so that it can be invoked from any terminal. **Contents:** - [Commands](#commands) - [Options for serve and run](#options-for-serve-and-run) - [Environment Variables](#environment-variables) | [Custom Backend Binaries](#custom-backend-binaries) | [API Key and Security](#api-key-and-security) - [Options for pull](#options-for-pull) - [Options for launch](#options-for-launch) - [Lemonade Desktop App](#lemonade-desktop-app) | [Remote Server Connection](#remote-server-connection) ## Commands `lemonade-server` provides these utilities: | Option/Command | Description | |---------------------|-------------------------------------| | `-v`, `--version` | Print the `lemonade-sdk` package version used to install Lemonade Server. | | `serve` | Start the server process in the current terminal. See command options [below](#options-for-serve-and-run). | | `status` | Check if server is running. If it is, print the port number. | | `stop` | Stop any running Lemonade Server process. | | `pull MODEL_NAME` | Install an LLM named `MODEL_NAME`. See [pull command options](#options-for-pull) for registering custom models. | | `run MODEL_NAME` | Start the server (if not already running) and chat with the specified model. Supports the same options as `serve`. | | `launch AGENT -m MODEL_NAME` | Launch a local coding agent (`claude` or `codex`) connected to a running Lemonade server. | | `list` | List all models. | | `delete MODEL_NAME` | Delete a model and its files from local storage. | Examples: ```bash # Start server with custom settings lemonade-server serve --port 8080 --log-level debug --llamacpp vulkan # Run a specific model with custom server settings lemonade-server run Qwen3-0.6B-GGUF --port 8080 --log-level debug --llamacpp rocm ``` ## Options for serve and run When using the `serve` command, you can configure the server with these additional options. The `run` command supports the same options but also requires a `MODEL_NAME` parameter: ```bash lemonade-server serve [options] lemonade-server run MODEL_NAME [options] ``` | Option | Description | Default | |--------------------------------|-------------------------------------|---------| | `--port [port]` | Specify the port number to run the server on | 8000 | | `--host [host]` | Specify the host address for where to listen connections | `localhost` | | `--log-level [level]` | Set the logging level | info | | `--no-tray` | Start server without the tray app (headless mode) | False | | `--llamacpp [vulkan\|rocm\cpu]` | Default LlamaCpp backend to use when loading models. Can be overridden per-model via the `/api/v1/load` endpoint. | vulkan | | `--ctx-size [size]` | Default context size for models. For llamacpp recipes, this sets the `--ctx-size` parameter for the llama server. For other recipes, prompts exceeding this size will be truncated. Can be overridden per-model via the `/api/v1/load` endpoint. | 4096 | | `--llamacpp-args [args]` | Default custom arguments to pass to llama-server. Must not conflict with arguments managed by Lemonade (e.g., `-m`, `--port`, `--ctx-size`, `-ngl`). Can be overridden per-model via the `/api/v1/load` endpoint. Example: `--llamacpp-args "--flash-attn on --no-mmap"` | "" | | `--flm-args [args]` | Custom arguments to pass to FLM (FastFlowLM) server. Must not conflict with arguments managed by Lemonade (e.g., `--host`, `--port`, `--ctx-len`). Commonly used for NPU concurrency tuning. Can be overridden per-model via the `/api/v1/load` endpoint. Example: `--flm-args "-s 20 -q 15"` (socket connections and queue length). | "" | | `--extra-models-dir [path]` | Experimental feature. Secondary directory to scan for LLM GGUF model files. Audio, embedding, reranking, and non-GGUF files are not supported, yet. | None | | `--max-loaded-models [N]` | Maximum number of models to keep loaded per type slot (LLMs, audio, image, etc.). Use `-1` for unlimited. Example: `--max-loaded-models 5` allows up to 5 of each model type simultaneously. | `1` | | `--global-timeout [seconds]` | Global default timeout for HTTP requests, inference, and readiness checks in seconds. This value sets the `CURLOPT_TIMEOUT` in the underlying HTTP client and overrides internal defaults for inference and backend startup. | 300 | | `--save-options` | Only available for the run command. Saves the context size, LlamaCpp backend and custom llama-server arguments as default for running this model. Unspecified values will be saved using their default value. | False | ### Timeout Configuration Lemonade uses a unified timeout strategy controlled by the `--global-timeout` CLI flag. This value ensures stability across different operations: | Timeout Name | Controlled By | Default | Description | |--------------|---------------|---------|-------------| | **Global HTTP Timeout** | `--global-timeout` | 300s | Sets the base timeout for all `curl` operations, including model downloads and management tasks. | | **Inference Timeout** | `--global-timeout` | 300s | Applied specifically to inference requests (chat, completion) to backends. For very long generations, increasing the global timeout may be necessary. | | **Readiness Timeout** | `--global-timeout` | 300s* | Maximum time the router waits for a backend server to become healthy after starting it. *Note: If not explicitly set, backends may use up to 600s for initial setup. | ### Environment Variables These settings can also be provided via environment variables that Lemonade Server recognizes regardless of launch method: | Environment Variable | Description | |------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------| | `LEMONADE_HOST` | Host address for where to listen for connections | | `LEMONADE_PORT` | Port number to run the server on | | `LEMONADE_LOG_LEVEL` | Logging level | | `LEMONADE_LLAMACPP` | Default LlamaCpp backend (`vulkan`, `rocm`, or `cpu`) | | `LEMONADE_WHISPERCPP` | Default WhisperCpp backend: `npu` or `cpu` on Windows; `cpu` or `vulkan` on Linux | | `LEMONADE_CTX_SIZE` | Default context size for models | | `LEMONADE_LLAMACPP_ARGS` | Custom arguments to pass to llama-server | | `LEMONADE_FLM_ARGS` | Custom arguments to pass to FLM server | | `LEMONADE_EXTRA_MODELS_DIR` | Secondary directory to scan for GGUF model files | | `LEMONADE_MAX_LOADED_MODELS` | Maximum number of models to keep loaded per type slot (LLMs, audio, image, etc.). Use `-1` for unlimited, or a positive integer. Default: `1` | | `LEMONADE_DISABLE_MODEL_FILTERING` | Set to `1` to disable hardware-based model filtering (e.g., RAM amount, NPU availability) and show all models regardless of system capabilities | | `LEMONADE_ENABLE_DGPU_GTT` | Set to `1` to include GTT for hardware-based model filtering | | `LEMONADE_GLOBAL_TIMEOUT` | Global default timeout for HTTP requests, inference, and readiness checks in seconds | #### Custom Backend Binaries You can provide your own `llama-server`, `whisper-server`, or `ryzenai-server` binary by setting the full path via the following environment variables: | Environment Variable | Description | |---------------------|-------------| | `LEMONADE_LLAMACPP_ROCM_BIN` | Path to custom `llama-server` binary for ROCm backend | | `LEMONADE_LLAMACPP_VULKAN_BIN` | Path to custom `llama-server` binary for Vulkan backend | | `LEMONADE_LLAMACPP_CPU_BIN` | Path to custom `llama-server` binary for CPU backend | | `LEMONADE_WHISPERCPP_CPU_BIN` | Path to custom `whisper-server` binary for CPU backend | | `LEMONADE_WHISPERCPP_NPU_BIN` | Path to custom `whisper-server` binary for NPU backend | | `LEMONADE_RYZENAI_SERVER_BIN` | Path to custom `ryzenai-server` binary for NPU/Hybrid models | **Note:** These environment variables do not override the `--llamacpp` option. They allow you to specify an alternative binary for specific backends while still using the standard backend selection mechanism. **Examples:** On Windows: ```cmd set LEMONADE_LLAMACPP_VULKAN_BIN=C:\path\to\my\llama-server.exe lemonade-server serve ``` On Linux: ```bash export LEMONADE_LLAMACPP_VULKAN_BIN=/path/to/my/llama-server lemonade-server serve ``` #### API Key and Security If you expose your server over a network you can use the `LEMONADE_API_KEY` environment variable to set an API key (use a random long string) that will be required to execute any request. The API key will be expected as HTTP Bearer authentication, which is compatible with the OpenAI API. **IMPORTANT**: If you need to access `lemonade-server` over the internet, do not expose it directly! You will also need to setup an HTTPS reverse proxy (such as nginx) and expose that instead, otherwise all communication will be in plaintext! ## Options for pull The `pull` command downloads and installs models. For models already in the [Lemonade Server registry](https://lemonade-server.ai/models.html), only the model name is required. To register and install custom models from Hugging Face, use the registration options below: ```bash lemonade-server pull [options] ``` | Option | Description | Required | |--------|-------------|----------| | `--checkpoint CHECKPOINT` | Hugging Face checkpoint in the format `org/model:variant`. For GGUF models, the variant (after the colon) is required. Examples: `unsloth/Qwen3-8B-GGUF:Q4_0`, `amd/Qwen3-4B-awq-quant-onnx-hybrid` | For custom models | | `--recipe RECIPE` | Inference recipe to use. Options: `llamacpp`, `flm`, `ryzenai-llm` | For custom models | | `--reasoning` | Mark the model as a reasoning model (e.g., DeepSeek-R1). Adds the 'reasoning' label to model metadata. | No | | `--vision` | Mark the model as a vision/multimodal model. Adds the 'vision' label to model metadata. | No | | `--embedding` | Mark the model as an embedding model. Adds the 'embeddings' label to model metadata. For use with the `/api/v1/embeddings` endpoint. | No | | `--reranking` | Mark the model as a reranking model. Adds the 'reranking' label to model metadata. For use with the `/api/v1/reranking` endpoint. | No | | `--mmproj FILENAME` | Multimodal projector file for GGUF vision models. Example: `mmproj-model-f16.gguf` | For vision models | **Notes:** - Custom model names must use the `user.` namespace prefix (e.g., `user.MyModel`) - GGUF models require a variant specified in the checkpoint after the colon - Use `lemonade-server pull --help` to see examples and detailed information **Examples:** ```bash # Install a registered model from the Lemonade Server registry lemonade-server pull Qwen3-0.6B-GGUF # Register and install a custom GGUF model lemonade-server pull user.Phi-4-Mini-GGUF \ --checkpoint unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M \ --recipe llamacpp # Register and install a vision model with multimodal projector lemonade-server pull user.Gemma-3-4b \ --checkpoint ggml-org/gemma-3-4b-it-GGUF:Q4_K_M \ --recipe llamacpp \ --vision \ --mmproj mmproj-model-f16.gguf # Register and install an embedding model lemonade-server pull user.nomic-embed \ --checkpoint nomic-ai/nomic-embed-text-v1-GGUF:Q4_K_S \ --recipe llamacpp \ --embedding ``` For more information about model formats and recipes, see the [API documentation](../lemonade_api.md) and the [server models guide](https://lemonade-server.ai/models.html). For details on the underlying JSON files (`user_models.json` and `recipe_options.json`), see the [Custom Model Configuration Guide](./custom-models.md). ## Options for launch Use the `launch` command to start a coding agent CLI connected to an already-running Lemonade server: ```bash lemonade-server launch -m [--llamacpp-args ARGS] [--use-recipe] [--host HOST] [--port PORT] ``` | Option | Description | Required | |--------|-------------|----------| | `agent` | Agent CLI to launch (`claude` or `codex`) | Yes | | `-m, --model MODEL_NAME` | Model name to preload and use in the agent session | Yes | | `--llamacpp-args ARGS` | Custom llama.cpp load arguments for launch. When set, launch defaults are skipped. | No | | `--use-recipe` | Use the model's saved `recipe_options.json` values instead of launch defaults. | No | | `--host HOST` | Server host (also supports `LEMONADE_HOST`) | No | | `--port PORT` | Server port (also supports `LEMONADE_PORT`) | No | Notes: - `launch` does not start the Lemonade server. It first checks `/api/version` on the target host/port and exits with an error if unreachable. - If `--port` is not provided and the host is local (`localhost`, `127.0.0.1`, `0.0.0.0`, or empty), Lemonade auto-discovers a running server port. - Model loading is started in a background thread so agent startup is immediate. - The launched agent process takes over the terminal and Lemonade waits for that process to exit. - For llama.cpp-backed models, `launch` sends `-b 16384 -ub 16384 -fa on` in the `/api/v1/load` request by default. These are conservative coding-oriented defaults chosen to improve prompt prefill throughput and keep Flash Attention enabled on hardware that supports it. - If you pass `--llamacpp-args`, Lemonade skips those defaults entirely and sends only the arguments you provide. This is the simplest way to tune for smaller GPUs, CPU-only systems, or stricter memory budgets. - If you pass `--use-recipe`, Lemonade does not send launch defaults, so the saved per-model settings in `recipe_options.json` can apply. This follows the `/api/v1/load` priority order documented in the server spec: explicit load-request values win over saved recipe values, which win over CLI or environment defaults. - `--llamacpp-args` still uses the same `/api/v1/load` field as the HTTP API, so the usual managed-argument restrictions for `llamacpp_args` still apply. Examples: ```bash # Launch Codex against a local running server (auto-detect port) lemonade-server launch codex -m Qwen3-Coder-Next-GGUF # Launch Claude against an explicit endpoint lemonade-server launch claude -m Qwen3-Coder-Next-GGUF --host 127.0.0.1 --port 8000 # Replace launch defaults with custom llama.cpp tuning lemonade-server launch codex -m Qwen3-Coder-Next-GGUF --llamacpp-args "-b 8192 -ub 4096 -fa off" # Prefer the model's saved recipe_options.json settings lemonade-server launch claude -m Qwen3-Coder-Next-GGUF --use-recipe ``` ## Lemonade Desktop App The Lemonade Desktop App provides a graphical interface for chatting with models and managing the server. When installed via the full installer, `lemonade-app` is added to your PATH for easy command-line access. ### Launching the App ```bash # Launch the app (connects to local server automatically) lemonade-app ``` By default, the app connects to a server running on `localhost` and automatically discovers the port. To connect to a remote server, change the app settings. ### Remote Server Connection To connect the app to a server running on a different machine: 1. **Start the server with network access** on the host machine: ```bash lemonade-server serve --host 0.0.0.0 --port 8000 ``` > **Note:** Using `--host 0.0.0.0` allows connections from other machines on the network. Only do this on trusted networks. You can use `LEMONADE_API_KEY` (see above) to manage access on your network. 2. **Launch the app** on the client machine and configure the endpoint through the UI: ```bash lemonade-app ``` The app automatically discovers and connects to a local server unless an endpoint is explicitly configured in the UI. ## Next Steps The [Lemonade Server integration guide](./server_integration.md) provides more information about how these commands can be used to integrate Lemonade Server into an application. lemonade-sdk-lemonade-d88f5d9/docs/server/server_integration.md000066400000000000000000000260671515430344400250030ustar00rootroot00000000000000# Integrating with Lemonade Server This guide provides instructions on how to integrate Lemonade Server into your application. There are two main ways in which Lemonade Server might integrate into apps: * User-Managed Server: User is responsible for installing and managing Lemonade Server. * App-Managed Server: App is responsible for installing and managing Lemonade Server on behalf of the user. The first part of this guide contains instructions that are common for both integration approaches. The second part provides advanced instructions only needed for app-managed server integrations. ## General Instructions ### Identifying Existing Installation To identify if Lemonade Server is installed on a system, you can use the [`lemonade-server` CLI command](./lemonade-server-cli.md), which is added to path when using our installer. This is a reliable method to: - Verify if the server is installed. - Check which version is currently available by running the command below. ``` lemonade-server --version ``` >Note: The `lemonade-server` CLI command is added to PATH when using the Windows Installer (lemonade-server-minimal.msi), Debian Installer (lemonade-server__amd64.deb), or macOS Installer (Lemonade--Darwin.pkg). ### Checking Server Status To identify whether or not the server is running anywhere on the system you may use the `status` command of `lemonade-server`. ``` lemonade-server status ``` This command will return either `Server is not running` or `Server is running on port `. ### Identifying Compatible Devices AMD Ryzen™ AI `Hybrid` and `NPU` models are available on Windows 11 on all AMD Ryzen™ AI 300 Series, 400 Series, and Z2 Series Processors. To programmatically identify supported devices, we recommend using a regular expression that checks if the CPU name converted to lowercase contains "ryzen ai" and either a 3-digit number starting with 3 or 4, or "z2" as shown below. ``` ryzen ai.*((\b[34]\d{2}\b)|(\bz2\b)) ``` Explanation: - `ryzen ai`: Matches the literal phrase "Ryzen AI". - `.*`: Allows any characters (including spaces) to appear after "Ryzen AI". - `((\b[34]\d{2}\b)|(\bz2\b))`: Matches either a three-digit number starting with 3 or 4 (for 300/400 series), or "z2" (for Z2 series like Z2 Extreme), ensuring it's a standalone word. There are several ways to check the CPU name on a Windows computer. A reliable way of doing so is through cmd's `reg query` command as shown below. ``` reg query "HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor\0" /v ProcessorNameString ``` Once you capture the CPU name, make sure to convert it to lowercase before using the regular expression. ### Downloading Server Installer The recommended way of directing users to the installer is pointing users to https://lemonade-server.ai/install_options.html ### Installing Additional Models If you want to install models on behalf of your users, the following tools are available: - LLMs that are already available in lemonade: - Run `lemonade-server list`. - Use the [models endpoint](./server_spec.md#get-apiv1models). - [A human-readable list of supported models](https://lemonade-server.ai/models.html). Do not modify this file in an existing install (see `user_models.json` below). - [A JSON file that defines the list of built-in models](https://github.com/lemonade-sdk/lemonade/blob/main/src/cpp/resources/server_models.json). - `lemonade-server pull MODEL` on the command line interface. - Adding new LLMs: - The `user_models.json` file is similar to `server_models.json` (see above), but contains a user-specific registry that persists across lemonade updates. It is located at `$LEMONADE_CACHE_DIR/user_models.json`, which defaults to `~/.cache/lemonade/user_models.json`. For a full template and field reference, see the [Custom Model Configuration Guide](./custom-models.md). - [The `pull` endpoint in the server](./server_spec.md#post-apiv1pull) automates the process of registering models into `user_models.json` and downloading them. - The `lemonade-server pull` CLI command can also register and download new models, see [Options for pull](./lemonade-server-cli.md#options-for-pull). ## Stand-Alone Server Integration Some apps might prefer to be responsible for installing and managing Lemonade Server on behalf of the user. This part of the guide includes steps for installing and running Lemonade Server so that your users don't have to install Lemonade Server separately. Definitions: - Command line usage allows the server process to be launched programmatically, so that your application can manage starting and stopping the server process on your user's behalf. - "Silent installation" refers to an automatic command for installing Lemonade Server without running any GUI or prompting the user for any questions. It does assume that the end-user fully accepts the license terms, so be sure that your own application makes this clear to the user. ### Command Line Invocation This command line invocation starts the Lemonade Server process so that your application can connect to it via REST API endpoints. To start the server, simply run the command below. ```bash lemonade-server serve ``` By default, the server runs on port 8000. Optionally, you can specify a custom port using the --port argument: ```bash lemonade-server serve --port 8123 ``` You can also prevent the server from showing a system tray icon by using the `--no-tray` flag (Windows and macOS): ```bash lemonade-server serve --no-tray ``` You can also run the server as a background process using a subprocess or any preferred method. To stop the server, you may use the `lemonade-server stop` command, or simply terminate the process you created by keeping track of its PID. Please do not run the `lemonade-server stop` command if your application has not started the server, as the server may be used by other applications. ## Windows Installation **Available Installers:** - `lemonade-server-minimal.msi` - Server only (~3 MB) - `lemonade.msi` - Full installer with Electron desktop app (~105 MB) **GUI Installation:** Double-click the MSI file, or run: ```bash msiexec /i lemonade.msi ``` **MSI Properties:** Properties can be passed on the command line to customize the installation: - `INSTALLDIR` - Custom installation directory (default: `%LOCALAPPDATA%\lemonade_server`) - `ADDDESKTOPSHORTCUT` - Create desktop shortcut (0=no, 1=yes, default: 1) - `ALLUSERS` - Install for all users (1=yes, requires elevation; default: per-user) **Examples:** ```bash # Custom install directory msiexec /i lemonade.msi INSTALLDIR="C:\Custom\Path" # Without desktop shortcut msiexec /i lemonade.msi ADDDESKTOPSHORTCUT=0 # Combined parameters msiexec /i lemonade.msi INSTALLDIR="C:\Custom\Path" ADDDESKTOPSHORTCUT=0 ``` ### Silent Installation Add `/qn` to run without a GUI, automatically accepting all prompts: ```bash msiexec /i lemonade.msi /qn ``` This can be combined with any MSI properties: ```bash msiexec /i lemonade.msi /qn INSTALLDIR="C:\Custom\Path" ADDDESKTOPSHORTCUT=0 ``` ### All Users Installation To install for all users (Program Files + system PATH), you **must** run from an Administrator command prompt. 1. Open Command Prompt as Administrator (right-click → "Run as administrator") 2. Run the install command: ```bash msiexec /i lemonade.msi ALLUSERS=1 INSTALLDIR="C:\Program Files (x86)\Lemonade Server" ``` For silent all-users installation, add `/qn`: ```bash msiexec /i lemonade.msi /qn ALLUSERS=1 INSTALLDIR="C:\Program Files (x86)\Lemonade Server" ``` **Troubleshooting:** - If installation fails silently, check that you're running as Administrator - Add `/L*V install.log` to generate a debug log file ## Linux Installation ### Debian/Ubuntu Package The Debian package installer handles all system configuration automatically, including setting up a systemd service for managing the Lemonade Server. If you would prefer to manage the lifecycle of the server process manually, the service can be disabled and manually run as well. ### Systemd Service Management When Lemonade Server is installed via the Debian package, it registers a systemd service called `lemonade-server` that allows you to manage the server process using standard systemd commands. **Service Features:** - **Automatic restart:** The service automatically restarts if the server crashes - **User isolation:** Runs under the unprivileged `lemonade` user for security - **GPU access:** The service is configured with proper group membership to access GPU/NPU hardware via the `render` group - **Security hardening:** Includes systemd security options like `ProtectSystem=full`, `ProtectHome=yes`, and `NoNewPrivileges=yes` - **Boot integration:** Starts automatically on system boot (if enabled) **Common Commands:** ```bash # Start the service sudo systemctl start lemonade-server # Stop the service sudo systemctl stop lemonade-server # Restart the service sudo systemctl restart lemonade-server # Check service status sudo systemctl status lemonade-server # Enable automatic startup on boot sudo systemctl enable lemonade-server # Disable automatic startup on boot sudo systemctl disable lemonade-server # View service logs sudo journalctl -u lemonade-server # View recent logs (follow mode) sudo journalctl -u lemonade-server -f ``` **Configuration:** The Lemonade Server systemd service is configured to read settings from `/etc/lemonade/lemonade.conf`. Environment variables defined in this file are passed to the server process. Edit this file to customize server behavior: ```bash sudo nano /etc/lemonade/lemonade.conf ``` Secrets, like the LEMONADE_API_KEY secret, are defined in `/etc/lemonade/secrets.conf` ```bash sudo nano /etc/lemonade/secrets.conf ``` After making changes to the configuration files, restart the service for changes to take effect: ```bash sudo systemctl restart lemonade-server ``` **Service File Location:** The systemd service file is located at `/etc/systemd/system/lemonade-server.service`. This file should not be edited directly as it may be overwritten during package updates. Instead, use the configuration file (`/etc/lemonade/lemonade.conf`) to customize server behavior. If you need to make persistent changes to the service file, use systemd's drop-in override mechanism: ```bash sudo systemctl edit lemonade-server ``` This creates an override file that takes precedence over the original service file and persists across updates. ## macOS Installation (Beta) **Available Installer:** - `Lemonade--Darwin.pkg` - Signed and notarized macOS installer package **GUI Installation:** Double-click the `.pkg` file to launch the installer, or run: ```bash sudo installer -pkg Lemonade--Darwin.pkg -target / ``` **Silent Installation:** ```bash sudo installer -pkg Lemonade--Darwin.pkg -target / ``` The macOS installer places binaries in `/usr/local/bin`, so `lemonade-server` is available in PATH immediately after installation. > **Note:** macOS support is currently in beta. The llama.cpp backend with Metal acceleration is supported on Apple Silicon Macs. lemonade-sdk-lemonade-d88f5d9/docs/server/server_spec.md000066400000000000000000002164211515430344400234050ustar00rootroot00000000000000# Lemonade Server Spec The Lemonade Server is a standards-compliant server process that provides an HTTP API to enable integration with other applications. Lemonade Server currently supports these backends: | Backend | Model Format | Description | |----------------------------------------------------------------------|--------------|----------------------------------------------------------------------------------------------------------------------------| | [Llama.cpp](https://github.com/ggml-org/llama.cpp) | `.GGUF` | Uses llama.cpp's `llama-server` backend. More details [here](#gguf-support). | | [ONNX Runtime GenAI (OGA)](https://github.com/microsoft/onnxruntime-genai) | `.ONNX` | Uses Lemonade's own `ryzenai-server` backend. | | [FastFlowLM](https://github.com/FastFlowLM/FastFlowLM) | `.q4nx` | Uses FLM's `flm serve` backend. More details [here](#fastflowlm-support). | | [whisper.cpp](https://github.com/ggerganov/whisper.cpp) | `.bin` | Uses whisper.cpp's `whisper-server` backend for audio transcription. Models: Whisper-Tiny, Whisper-Base, Whisper-Small. | | [stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp) | `.safetensors` | Uses sd.cpp's `sd-cli` backend for image generation. Models: SD-Turbo, SDXL-Turbo, etc. | | [Kokoros](https://github.com/lucasjinreal/Kokoros) | `.onnx` | Uses Kokoro's `koko` backend for speech generation. Models: kokoro-v1 | ## Endpoints Overview The [key endpoints of the OpenAI API](#openai-compatible-endpoints) are available. We are also actively investigating and developing [additional endpoints](#lemonade-specific-endpoints) that will improve the experience of local applications. ### OpenAI-Compatible Endpoints - POST `/api/v1/chat/completions` - Chat Completions (messages -> completion) - POST `/api/v1/completions` - Text Completions (prompt -> completion) - POST `/api/v1/embeddings` - Embeddings (text -> vector representations) - POST `/api/v1/responses` - Chat Completions (prompt|messages -> event) - POST `/api/v1/audio/transcriptions` - Audio Transcription (audio file -> text) - POST `/api/v1/audio/speech` - Text to speech (text -> audio) - WS `/realtime` - Realtime Audio Transcription (streaming audio -> text, OpenAI SDK compatible) - POST `/api/v1/images/generations` - Image Generation (prompt -> image) - POST `/api/v1/images/edits` - Image Editing (image + prompt -> edited image) - POST `/api/v1/images/variations` - Image Variations (image -> varied image) - GET `/api/v1/models` - List models available locally - GET `/api/v1/models/{model_id}` - Retrieve a specific model by ID ### llama.cpp Endpoints These endpoints defined by `llama.cpp` extend the OpenAI-compatible API with additional functionality. - POST `/api/v1/reranking` - Reranking (query + documents -> relevance-scored documents) ### Lemonade-Specific Endpoints We have designed a set of Lemonade-specific endpoints to enable client applications by extending the existing cloud-focused APIs (e.g., OpenAI). These extensions allow for a greater degree of UI/UX responsiveness in native applications by allowing applications to: - Download models at setup time. - Pre-load models at UI-loading-time, as opposed to completion-request time. - Unload models to save memory space. - Understand system resources and state to make dynamic choices. The additional endpoints are: - POST `/api/v1/install` - Install or update a backend - POST `/api/v1/uninstall` - Remove a backend - POST `/api/v1/pull` - Install a model - POST `/api/v1/delete` - Delete a model - POST `/api/v1/load` - Load a model - POST `/api/v1/unload` - Unload a model - GET `/api/v1/health` - Check server status, such as models loaded - GET `/api/v1/stats` - Performance statistics from the last request - GET `/api/v1/system-info` - System information and device enumeration - GET `/live` - Check server liveness for load balancers and orchestrators ### Ollama-Compatible API Lemonade supports the [Ollama API](https://github.com/ollama/ollama/blob/main/docs/api.md), allowing applications built for Ollama to work with Lemonade without modification. To enable auto-detection by Ollama-integrated apps, launch the server on the Ollama default port: ```bash lemonade-server serve --port 11434 ``` | Endpoint | Status | Notes | |----------|--------|-------| | `POST /api/chat` | Supported | Streaming and non-streaming | | `POST /api/generate` | Supported | Text completion + image generation | | `GET /api/tags` | Supported | Lists downloaded models | | `POST /api/show` | Supported | Model details | | `DELETE /api/delete` | Supported | | | `POST /api/pull` | Supported | Download with progress | | `POST /api/embed` | Supported | New embeddings format | | `POST /api/embeddings` | Supported | Legacy embeddings | | `GET /api/ps` | Supported | Running models | | `GET /api/version` | Supported | | | `POST /api/create` | Not supported | Returns 501 | | `POST /api/copy` | Not supported | Returns 501 | | `POST /api/push` | Not supported | Returns 501 | ### Anthropic-Compatible API (Initial) Lemonade supports an initial Anthropic Messages compatibility endpoint for applications that call Claude-style APIs. | Endpoint | Status | Notes | |----------|--------|-------| | `POST /v1/messages` | Supported | Supports both streaming and non-streaming. Query params like `?beta=true` are accepted. | Current scope focuses on message generation parity for common fields (`model`, `messages`, `system`, `max_tokens`, `temperature`, `stream`, and basic `tools`). Unsupported or unimplemented Anthropic-specific fields are ignored and surfaced via warning logs/headers. ## Multi-Model Support Lemonade Server supports loading multiple models simultaneously, allowing you to keep frequently-used models in memory for faster switching. The server uses a Least Recently Used (LRU) cache policy to automatically manage model eviction when limits are reached. ### Configuration Use the `--max-loaded-models` option to specify how many models to keep loaded per type slot: ```bash # Allow up to 5 models of each type (5 LLMs, 5 embedding, 5 reranking, 5 audio, 5 image) lemonade-server serve --max-loaded-models 5 # Unlimited models (no LRU eviction) lemonade-server serve --max-loaded-models -1 ``` **Default:** `1` (one model of each type). Use `-1` for unlimited. ### Model Types Models are categorized into these types: - **LLM** - Chat and completion models (default type) - **Embedding** - Models for generating text embeddings (identified by the `embeddings` label) - **Reranking** - Models for document reranking (identified by the `reranking` label) - **Audio** - Models for audio transcription using Whisper (identified by the `audio` label) - **Image** - Models for image generation (identified by the `image` label) Each type has its own independent LRU cache, all sharing the same slot limit set by `--max-loaded-models`. ### Device Constraints - **NPU Exclusivity:** Only one model can use the NPU at a time. Loading a new NPU model will evict any existing NPU model regardless of type or limits. - **CPU/GPU:** No inherent limits beyond available RAM. Multiple models can coexist on CPU or GPU. ### Eviction Policy When a model slot is full: 1. The least recently used model of that type is evicted 2. The new model is loaded 3. If loading fails (except file-not-found errors), all models are evicted and the load is retried Models currently processing inference requests cannot be evicted until they finish. ### Per-Model Settings Each model can be loaded with custom settings (context size, llamacpp backend, llamacpp args) via the `/api/v1/load` endpoint. These per-model settings override the default values set via CLI arguments or environment variables. See the [`/api/v1/load` endpoint documentation](#post-apiv1load) for details. **Setting Priority Order:** 1. Values passed explicitly in `/api/v1/load` request (highest priority) 2. Values from `lemonade-server` CLI arguments or environment variables 3. Hardcoded defaults in `lemonade-router` (lowest priority) ## Start the HTTP Server > **NOTE:** This server is intended for use on local systems only. Do not expose the server port to the open internet. See the [Lemonade Server getting started instructions](./README.md). ```bash lemonade-server serve ``` ## OpenAI-Compatible Endpoints ### `POST /api/v1/chat/completions` ![Status](https://img.shields.io/badge/status-partially_available-green) Chat Completions API. You provide a list of messages and receive a completion. This API will also load the model if it is not already loaded. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `messages` | Yes | Array of messages in the conversation. Each message should have a `role` ("user" or "assistant") and `content` (the message text). | ![Status](https://img.shields.io/badge/available-green) | | `model` | Yes | The model to use for the completion. | ![Status](https://img.shields.io/badge/available-green) | | `stream` | No | If true, tokens will be sent as they are generated. If false, the response will be sent as a single message once complete. Defaults to false. | ![Status](https://img.shields.io/badge/available-green) | | `stop` | No | Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. Can be a string or an array of strings. | ![Status](https://img.shields.io/badge/available-green) | | `logprobs` | No | Include log probabilities of the output tokens. If true, returns the log probability of each output token. Defaults to false. | ![Status](https://img.shields.io/badge/not_available-red) | | `temperature` | No | What sampling temperature to use. | ![Status](https://img.shields.io/badge/available-green) | | `repeat_penalty` | No | Number between 1.0 and 2.0. 1.0 means no penalty. Higher values discourage repetition. | ![Status](https://img.shields.io/badge/available-green) | | `top_k` | No | Integer that controls the number of top tokens to consider during sampling. | ![Status](https://img.shields.io/badge/available-green) | | `top_p` | No | Float between 0.0 and 1.0 that controls the cumulative probability of top tokens to consider during nucleus sampling. | ![Status](https://img.shields.io/badge/available-green) | | `tools` | No | A list of tools the model may call. | ![Status](https://img.shields.io/badge/available-green) | | `max_tokens` | No | An upper bound for the number of tokens that can be generated for a completion. Mutually exclusive with `max_completion_tokens`. This value is now deprecated by OpenAI in favor of `max_completion_tokens` | ![Status](https://img.shields.io/badge/available-green) | | `max_completion_tokens` | No | An upper bound for the number of tokens that can be generated for a completion. Mutually exclusive with `max_tokens`. | ![Status](https://img.shields.io/badge/available-green) | #### Example request === "PowerShell" ```powershell Invoke-WebRequest ` -Uri "http://localhost:8000/api/v1/chat/completions" ` -Method POST ` -Headers @{ "Content-Type" = "application/json" } ` -Body '{ "model": "Qwen3-0.6B-GGUF", "messages": [ { "role": "user", "content": "What is the population of Paris?" } ], "stream": false }' ``` === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen3-0.6B-GGUF", "messages": [ {"role": "user", "content": "What is the population of Paris?"} ], "stream": false }' ``` #### Image understanding input format (OpenAI-compatible) To send images to `chat/completions`, pass a `messages[*].content` array that mixes `text` and `image_url` items. The image can be provided as a base64 data URL (for example, from `FileReader.readAsDataURL(...)` in web apps). ```bash curl -X POST http://localhost:8000/api/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen2.5-VL-7B-Instruct", "messages": [ { "role": "user", "content": [ {"type": "text", "text": "What is in this image?"}, {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..."}} ] } ], "stream": false }' ``` #### Response format === "Non-streaming responses" ```json { "id": "0", "object": "chat.completion", "created": 1742927481, "model": "Qwen3-0.6B-GGUF", "choices": [{ "index": 0, "message": { "role": "assistant", "content": "Paris has a population of approximately 2.2 million people in the city proper." }, "finish_reason": "stop" }] } ``` === "Streaming responses" For streaming responses, the API returns a stream of server-sent events (however, Open AI recommends using their streaming libraries for parsing streaming responses): ```json { "id": "0", "object": "chat.completion.chunk", "created": 1742927481, "model": "Qwen3-0.6B-GGUF", "choices": [{ "index": 0, "delta": { "role": "assistant", "content": "Paris" } }] } ``` ### `POST /api/v1/completions` ![Status](https://img.shields.io/badge/status-fully_available-green) Text Completions API. You provide a prompt and receive a completion. This API will also load the model if it is not already loaded. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `prompt` | Yes | The prompt to use for the completion. | ![Status](https://img.shields.io/badge/available-green) | | `model` | Yes | The model to use for the completion. | ![Status](https://img.shields.io/badge/available-green) | | `stream` | No | If true, tokens will be sent as they are generated. If false, the response will be sent as a single message once complete. Defaults to false. | ![Status](https://img.shields.io/badge/available-green) | | `stop` | No | Up to 4 sequences where the API will stop generating further tokens. The returned text will not contain the stop sequence. Can be a string or an array of strings. | ![Status](https://img.shields.io/badge/available-green) | | `echo` | No | Echo back the prompt in addition to the completion. Available on non-streaming mode. | ![Status](https://img.shields.io/badge/available-green) | | `logprobs` | No | Include log probabilities of the output tokens. If true, returns the log probability of each output token. Defaults to false. Only available when `stream=False`. | ![Status](https://img.shields.io/badge/available-green) | | `temperature` | No | What sampling temperature to use. | ![Status](https://img.shields.io/badge/available-green) | | `repeat_penalty` | No | Number between 1.0 and 2.0. 1.0 means no penalty. Higher values discourage repetition. | ![Status](https://img.shields.io/badge/available-green) | | `top_k` | No | Integer that controls the number of top tokens to consider during sampling. | ![Status](https://img.shields.io/badge/available-green) | | `top_p` | No | Float between 0.0 and 1.0 that controls the cumulative probability of top tokens to consider during nucleus sampling. | ![Status](https://img.shields.io/badge/available-green) | | `max_tokens` | No | An upper bound for the number of tokens that can be generated for a completion, including input tokens. | ![Status](https://img.shields.io/badge/available-green) | #### Example request === "PowerShell" ```powershell Invoke-WebRequest -Uri "http://localhost:8000/api/v1/completions" ` -Method POST ` -Headers @{ "Content-Type" = "application/json" } ` -Body '{ "model": "Qwen3-0.6B-GGUF", "prompt": "What is the population of Paris?", "stream": false }' ``` === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen3-0.6B-GGUF", "prompt": "What is the population of Paris?", "stream": false }' ``` #### Response format The following format is used for both streaming and non-streaming responses: ```json { "id": "0", "object": "text_completion", "created": 1742927481, "model": "Qwen3-0.6B-GGUF", "choices": [{ "index": 0, "text": "Paris has a population of approximately 2.2 million people in the city proper.", "finish_reason": "stop" }], } ``` ### `POST /api/v1/embeddings` ![Status](https://img.shields.io/badge/status-fully_available-green) Embeddings API. You provide input text and receive vector representations (embeddings) that can be used for semantic search, clustering, and similarity comparisons. This API will also load the model if it is not already loaded. > **Note:** This endpoint is only available for models using the `llamacpp` or `flm` recipes. ONNX models (OGA recipes) do not support embeddings. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `input` | Yes | The input text or array of texts to embed. Can be a string or an array of strings. | ![Status](https://img.shields.io/badge/available-green) | | `model` | Yes | The model to use for generating embeddings. | ![Status](https://img.shields.io/badge/available-green) | | `encoding_format` | No | The format to return embeddings in. Supported values: `"float"` (default), `"base64"`. | ![Status](https://img.shields.io/badge/available-green) | #### Example request === "PowerShell" ```powershell Invoke-WebRequest ` -Uri "http://localhost:8000/api/v1/embeddings" ` -Method POST ` -Headers @{ "Content-Type" = "application/json" } ` -Body '{ "model": "nomic-embed-text-v1-GGUF", "input": ["Hello, world!", "How are you?"], "encoding_format": "float" }' ``` === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/embeddings \ -H "Content-Type: application/json" \ -d '{ "model": "nomic-embed-text-v1-GGUF", "input": ["Hello, world!", "How are you?"], "encoding_format": "float" }' ``` #### Response format ```json { "object": "list", "data": [ { "object": "embedding", "index": 0, "embedding": [0.0234, -0.0567, 0.0891, ...] }, { "object": "embedding", "index": 1, "embedding": [0.0456, -0.0678, 0.1234, ...] } ], "model": "nomic-embed-text-v1-GGUF", "usage": { "prompt_tokens": 12, "total_tokens": 12 } } ``` **Field Descriptions:** - `object` - Type of response object, always `"list"` - `data` - Array of embedding objects - `object` - Type of embedding object, always `"embedding"` - `index` - Index position of the input text in the request - `embedding` - Vector representation as an array of floats - `model` - Model identifier used to generate the embeddings - `usage` - Token usage statistics - `prompt_tokens` - Number of tokens in the input - `total_tokens` - Total tokens processed ### `POST /api/v1/reranking` ![Status](https://img.shields.io/badge/status-fully_available-green) Reranking API. You provide a query and a list of documents, and receive the documents reordered by their relevance to the query with relevance scores. This is useful for improving search results quality. This API will also load the model if it is not already loaded. > **Note:** This endpoint follows API conventions similar to OpenAI's format but is not part of the official OpenAI API. It is inspired by llama.cpp and other inference server implementations. > **Note:** This endpoint is only available for models using the `llamacpp` recipe. It is not available for FLM or ONNX models. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `query` | Yes | The search query text. | ![Status](https://img.shields.io/badge/available-green) | | `documents` | Yes | Array of document strings to be reranked. | ![Status](https://img.shields.io/badge/available-green) | | `model` | Yes | The model to use for reranking. | ![Status](https://img.shields.io/badge/available-green) | #### Example request === "PowerShell" ```powershell Invoke-WebRequest ` -Uri "http://localhost:8000/api/v1/reranking" ` -Method POST ` -Headers @{ "Content-Type" = "application/json" } ` -Body '{ "model": "bge-reranker-v2-m3-GGUF", "query": "What is the capital of France?", "documents": [ "Paris is the capital of France.", "Berlin is the capital of Germany.", "Madrid is the capital of Spain." ] }' ``` === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/reranking \ -H "Content-Type: application/json" \ -d '{ "model": "bge-reranker-v2-m3-GGUF", "query": "What is the capital of France?", "documents": [ "Paris is the capital of France.", "Berlin is the capital of Germany.", "Madrid is the capital of Spain." ] }' ``` #### Response format ```json { "model": "bge-reranker-v2-m3-GGUF", "object": "list", "results": [ { "index": 0, "relevance_score": 8.60673713684082 }, { "index": 1, "relevance_score": -5.3886260986328125 }, { "index": 2, "relevance_score": -3.555561065673828 } ], "usage": { "prompt_tokens": 51, "total_tokens": 51 } } ``` **Field Descriptions:** - `model` - Model identifier used for reranking - `object` - Type of response object, always `"list"` - `results` - Array of all documents with relevance scores - `index` - Original index of the document in the input array - `relevance_score` - Relevance score assigned by the model (higher = more relevant) - `usage` - Token usage statistics - `prompt_tokens` - Number of tokens in the input - `total_tokens` - Total tokens processed > **Note:** The results are returned in their original input order, not sorted by relevance score. To get documents ranked by relevance, you need to sort the results by `relevance_score` in descending order on the client side. ### `POST /api/v1/responses` ![Status](https://img.shields.io/badge/status-partially_available-green) Responses API. You provide an input and receive a response. This API will also load the model if it is not already loaded. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `input` | Yes | A list of dictionaries or a string input for the model to respond to. | ![Status](https://img.shields.io/badge/available-green) | | `model` | Yes | The model to use for the response. | ![Status](https://img.shields.io/badge/available-green) | | `max_output_tokens` | No | The maximum number of output tokens to generate. | ![Status](https://img.shields.io/badge/available-green) | | `temperature` | No | What sampling temperature to use. | ![Status](https://img.shields.io/badge/available-green) | | `repeat_penalty` | No | Number between 1.0 and 2.0. 1.0 means no penalty. Higher values discourage repetition. | ![Status](https://img.shields.io/badge/available-green) | | `top_k` | No | Integer that controls the number of top tokens to consider during sampling. | ![Status](https://img.shields.io/badge/available-green) | | `top_p` | No | Float between 0.0 and 1.0 that controls the cumulative probability of top tokens to consider during nucleus sampling. | ![Status](https://img.shields.io/badge/available-green) | | `stream` | No | If true, tokens will be sent as they are generated. If false, the response will be sent as a single message once complete. Defaults to false. | ![Status](https://img.shields.io/badge/available-green) | #### Streaming Events The Responses API uses semantic events for streaming. Each event is typed with a predefined schema, so you can listen for events you care about. Our initial implementation only offers support to: - `response.created` - `response.output_text.delta` - `response.completed` For a full list of event types, see the [API reference for streaming](https://platform.openai.com/docs/api-reference/responses-streaming). #### Example request === "PowerShell" ```powershell Invoke-WebRequest -Uri "http://localhost:8000/api/v1/responses" ` -Method POST ` -Headers @{ "Content-Type" = "application/json" } ` -Body '{ "model": "Llama-3.2-1B-Instruct-Hybrid", "input": "What is the population of Paris?", "stream": false }' ``` === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/responses \ -H "Content-Type: application/json" \ -d '{ "model": "Llama-3.2-1B-Instruct-Hybrid", "input": "What is the population of Paris?", "stream": false }' ``` #### Response format === "Non-streaming responses" ```json { "id": "0", "created_at": 1746225832.0, "model": "Llama-3.2-1B-Instruct-Hybrid", "object": "response", "output": [{ "id": "0", "content": [{ "annotations": [], "text": "Paris has a population of approximately 2.2 million people in the city proper." }] }] } ``` === "Streaming Responses" For streaming responses, the API returns a series of events. Refer to [OpenAI streaming guide](https://platform.openai.com/docs/guides/streaming-responses?api-mode=responses) for details. ### `POST /api/v1/audio/transcriptions` ![Status](https://img.shields.io/badge/status-partial-yellow) Audio Transcription API. You provide an audio file and receive a text transcription. This API will also load the model if it is not already loaded. > **Note:** This endpoint uses [whisper.cpp](https://github.com/ggerganov/whisper.cpp) as the backend. Whisper models are automatically downloaded when first used. > > **Limitations:** Only `wav` audio format and `json` response format are currently supported. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `file` | Yes | The audio file to transcribe. Supported formats: wav. | ![Status](https://img.shields.io/badge/partial-yellow) | | `model` | Yes | The Whisper model to use for transcription (e.g., `Whisper-Tiny`, `Whisper-Base`, `Whisper-Small`). | ![Status](https://img.shields.io/badge/available-green) | | `language` | No | The language of the audio (ISO 639-1 code, e.g., `en`, `es`, `fr`). If not specified, Whisper will auto-detect the language. | ![Status](https://img.shields.io/badge/available-green) | | `response_format` | No | The format of the response. Currently only `json` is supported. | ![Status](https://img.shields.io/badge/available-green) | #### Example request === "Windows" ```bash curl -X POST http://localhost:8000/api/v1/audio/transcriptions ^ -F "file=@C:\path\to\audio.wav" ^ -F "model=Whisper-Tiny" ``` === "Linux" ```bash curl -X POST http://localhost:8000/api/v1/audio/transcriptions \ -F "file=@/path/to/audio.wav" \ -F "model=Whisper-Tiny" ``` #### Response format ```json { "text": "Hello, this is a sample transcription of the audio file." } ``` **Field Descriptions:** - `text` - The transcribed text from the audio file ### `WS /realtime` ![Status](https://img.shields.io/badge/status-partial-yellow) Realtime Audio Transcription API via WebSocket (OpenAI SDK compatible). Stream audio from a microphone and receive transcriptions in real-time with Voice Activity Detection (VAD). > **Limitations:** Only 16kHz mono PCM16 audio format is supported. Uses the same Whisper models as the HTTP transcription endpoint. #### Connection The WebSocket server runs on a dynamically assigned port. Discover the port via the [`/api/v1/health`](#get-apiv1health) endpoint (`websocket_port` field), then connect with the model name: ``` ws://localhost:/realtime?model=Whisper-Tiny ``` Upon connection, the server sends a `session.created` message with a session ID. #### Client → Server Messages | Message Type | Description | |--------------|-------------| | `session.update` | Configure the session (set model, VAD settings) | | `input_audio_buffer.append` | Send audio data (base64-encoded PCM16) | | `input_audio_buffer.commit` | Force transcription of buffered audio | | `input_audio_buffer.clear` | Clear audio buffer without transcribing | #### Server → Client Messages | Message Type | Description | |--------------|-------------| | `session.created` | Session established, contains session ID | | `session.updated` | Session configuration updated | | `input_audio_buffer.speech_started` | VAD detected speech start | | `input_audio_buffer.speech_stopped` | VAD detected speech end, transcription triggered | | `input_audio_buffer.committed` | Audio buffer committed for transcription | | `input_audio_buffer.cleared` | Audio buffer cleared | | `conversation.item.input_audio_transcription.delta` | Interim/partial transcription (replaceable) | | `conversation.item.input_audio_transcription.completed` | Final transcription result | | `error` | Error message | #### Example: Configure Session ```json { "type": "session.update", "session": { "model": "Whisper-Tiny" } } ``` #### Example: Send Audio ```json { "type": "input_audio_buffer.append", "audio": "" } ``` Audio should be: - 16kHz sample rate - Mono (single channel) - 16-bit signed integer (PCM16) - Base64 encoded - Sent in chunks (~85ms recommended) #### Example: Transcription Result ```json { "type": "conversation.item.input_audio_transcription.completed", "transcript": "Hello, this is a test transcription." } ``` #### VAD Configuration VAD settings can be configured via `session.update`: ```json { "type": "session.update", "session": { "model": "Whisper-Tiny", "turn_detection": { "threshold": 0.01, "silence_duration_ms": 800, "prefix_padding_ms": 250 } } } ``` | Parameter | Default | Description | |-----------|---------|-------------| | `threshold` | 0.01 | RMS energy threshold for speech detection | | `silence_duration_ms` | 800 | Silence duration to trigger speech end | | `prefix_padding_ms` | 250 | Minimum speech duration before triggering | #### Code Examples See the [`examples/`](../../examples/) directory for a complete, runnable example: - **[`realtime_transcription.py`](../../examples/realtime_transcription.py)** - Python CLI for microphone streaming ```bash # Stream from microphone python examples/realtime_transcription.py --model Whisper-Tiny ``` #### Integration Notes - **Audio Format**: Server expects 16kHz mono PCM16. Higher sample rates must be downsampled client-side. - **Chunk Size**: Send audio in ~85-256ms chunks for optimal latency/efficiency. - **VAD Behavior**: Server automatically detects speech boundaries and triggers transcription on speech end. - **Manual Commit**: Use `input_audio_buffer.commit` to force transcription (e.g., when user clicks "stop"). - **Clear Buffer**: Use `input_audio_buffer.clear` to discard audio without transcribing. - **Chunking**: We are still tuning the chunking to balance latency vs. accuracy. ### `POST /api/v1/images/generations` ![Status](https://img.shields.io/badge/status-fully_available-green) Image Generation API. You provide a text prompt and receive a generated image. This API uses [stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp) as the backend. > **Note:** Image generation uses Stable Diffusion models. Available models include `SD-Turbo` (fast, ~4 steps), `SDXL-Turbo`, `SD-1.5`, and `SDXL-Base-1.0`. > > **Performance:** CPU inference takes ~4-5 minutes per image. GPU (Vulkan) is faster but may have compatibility issues with some hardware. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `prompt` | Yes | The text description of the image to generate. | ![Status](https://img.shields.io/badge/available-green) | | `model` | Yes | The Stable Diffusion model to use (e.g., `SD-Turbo`, `SDXL-Turbo`). | ![Status](https://img.shields.io/badge/available-green) | | `size` | No | The size of the generated image. Format: `WIDTHxHEIGHT` (e.g., `512x512`, `256x256`). Default: `512x512`. | ![Status](https://img.shields.io/badge/available-green) | | `n` | No | Number of images to generate. Currently only `1` is supported. | ![Status](https://img.shields.io/badge/partial-yellow) | | `response_format` | No | Format of the response. Only `b64_json` (base64-encoded image) is supported. | ![Status](https://img.shields.io/badge/partial-yellow) | | `steps` | No | Number of inference steps. SD-Turbo works well with 4 steps. Default varies by model. | ![Status](https://img.shields.io/badge/available-green) | | `cfg_scale` | No | Classifier-free guidance scale. SD-Turbo uses low values (~1.0). Default varies by model. | ![Status](https://img.shields.io/badge/available-green) | | `seed` | No | Random seed for reproducibility. If not specified, a random seed is used. | ![Status](https://img.shields.io/badge/available-green) | #### Example request === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/images/generations \ -H "Content-Type: application/json" \ -d '{ "model": "SD-Turbo", "prompt": "A serene mountain landscape at sunset", "size": "512x512", "steps": 4, "response_format": "b64_json" }' ``` ### `POST /api/v1/images/edits` ![Status](https://img.shields.io/badge/status-fully_available-green) Image Editing API. You provide a source image and a text prompt describing the desired change, and receive an edited image. This API uses [stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp) as the backend. > **Note:** This endpoint accepts `multipart/form-data` requests (not JSON). Use editing-capable models such as `Flux-2-Klein-4B` or `SD-Turbo`. > > **Performance:** CPU inference takes several minutes per image. GPU (ROCm) is significantly faster. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `model` | Yes | The Stable Diffusion model to use (e.g., `Flux-2-Klein-4B`, `SD-Turbo`). | ![Status](https://img.shields.io/badge/available-green) | | `image` | Yes | The source image file to edit (PNG). Sent as a file in multipart/form-data. | ![Status](https://img.shields.io/badge/available-green) | | `prompt` | Yes | A text description of the desired edit. | ![Status](https://img.shields.io/badge/available-green) | | `mask` | No | An optional mask image (PNG). White areas indicate regions to edit; black areas are preserved. | ![Status](https://img.shields.io/badge/available-green) | | `size` | No | The size of the output image. Format: `WIDTHxHEIGHT` (e.g., `512x512`). Default: `512x512`. | ![Status](https://img.shields.io/badge/available-green) | | `n` | No | Number of images to generate. Allowed range: `1`–`10`. Default: `1`. Values outside this range are rejected with `400 Bad Request`. | ![Status](https://img.shields.io/badge/partial-yellow) | | `response_format` | No | Format of the response. Only `b64_json` (base64-encoded image) is supported. | ![Status](https://img.shields.io/badge/partial-yellow) | | `steps` | No | Number of inference steps. Default varies by model. | ![Status](https://img.shields.io/badge/available-green) | | `cfg_scale` | No | Classifier-free guidance scale. Default varies by model. | ![Status](https://img.shields.io/badge/available-green) | | `seed` | No | Random seed for reproducibility. | ![Status](https://img.shields.io/badge/available-green) | | `user` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | ![Status](https://img.shields.io/badge/not_available-red) | | `background` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | ![Status](https://img.shields.io/badge/not_available-red) | | `quality` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | ![Status](https://img.shields.io/badge/not_available-red) | | `input_fidelity` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | ![Status](https://img.shields.io/badge/not_available-red) | | `output_compression` | No | OpenAI API compatibility field. Accepted; silently ignored by the backend. | ![Status](https://img.shields.io/badge/not_available-red) | #### Example request === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/images/edits \ -F "model=Flux-2-Klein-4B" \ -F "prompt=Add a red barn and mountains in the background, photorealistic" \ -F "size=512x512" \ -F "n=1" \ -F "response_format=b64_json" \ -F "image=@/path/to/source_image.png" ``` === "Python (OpenAI client)" ```python from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/api/v1", api_key="not-needed") with open("source_image.png", "rb") as image_file: response = client.images.edit( model="Flux-2-Klein-4B", image=image_file, prompt="Add a red barn and mountains in the background, photorealistic", size="512x512", ) import base64 image_data = base64.b64decode(response.data[0].b64_json) open("edited_image.png", "wb").write(image_data) ``` --- ### `POST /api/v1/images/variations` ![Status](https://img.shields.io/badge/status-fully_available-green) Image Variations API. You provide a source image and receive a variation of it. This API uses [stable-diffusion.cpp](https://github.com/leejet/stable-diffusion.cpp) as the backend. > **Note:** This endpoint accepts `multipart/form-data` requests (not JSON). Unlike `/images/edits`, a `prompt` parameter is not supported and will be ignored — the model generates a variation based solely on the input image. > > **Performance:** CPU inference takes several minutes per image. GPU (ROCm) is significantly faster. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `model` | Yes | The Stable Diffusion model to use (e.g., `Flux-2-Klein-4B`, `SD-Turbo`). | ![Status](https://img.shields.io/badge/available-green) | | `image` | Yes | The source image file (PNG). Sent as a file in multipart/form-data. | ![Status](https://img.shields.io/badge/available-green) | | `size` | No | The size of the output image. Format: `WIDTHxHEIGHT` (e.g., `512x512`). Default: `512x512`. | ![Status](https://img.shields.io/badge/available-green) | | `n` | No | Number of variations to generate. Integer between 1 and 10 inclusive. Default: `1`. Values outside this range result in a 400 Bad Request error. | ![Status](https://img.shields.io/badge/partial-yellow) | | `response_format` | No | Format of the response. Only `b64_json` (base64-encoded image) is supported. | ![Status](https://img.shields.io/badge/partial-yellow) | | `user` | No | OpenAI API compatibility field. Accepted but not forwarded to the backend. | ![Status](https://img.shields.io/badge/not_available-red) | #### Example request === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/images/variations \ -F "model=Flux-2-Klein-4B" \ -F "size=512x512" \ -F "n=1" \ -F "response_format=b64_json" \ -F "image=@/path/to/source_image.png" ``` === "Python (OpenAI client)" ```python from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/api/v1", api_key="not-needed") with open("source_image.png", "rb") as image_file: response = client.images.create_variation( model="Flux-2-Klein-4B", image=image_file, size="512x512", n=1, ) import base64 image_data = base64.b64decode(response.data[0].b64_json) open("variation.png", "wb").write(image_data) ``` --- ### `POST /api/v1/audio/speech` ![Status](https://img.shields.io/badge/status-fully_available-green) Speech Generation API. You provide a text input and receive an audio file. This API uses [Kokoros](https://github.com/lucasjinreal/Kokoros) as the backend. > **Note:** The model to use is called `kokoro-v1`. No other model is supported at the moment. > > **Limitations:** Only `mp3`, `wav`, `opus`, and `pcm` are supported. Streaming is supported in `audio` (`pcm`) mode. #### Parameters | Parameter | Required | Description | Status | |-----------|----------|-------------|--------| | `input` | Yes | The text to speak. | ![Status](https://img.shields.io/badge/available-green) | | `model` | Yes | The model to use (e.g., `kokoro-v1`). | ![Status](https://img.shields.io/badge/available-green) | | `speed` | No | Speaking speed. Default: `1.0`. | ![Status](https://img.shields.io/badge/available-green) | | `voice` | No | The voice to use. All OpenAI-defined voices can be used (`alloy`, `ash`, ...), as well as those defined by the kokoro model (`af_sky`, `am_echo`, ...). Default: `shimmer` | ![Status](https://img.shields.io/badge/partial-yellow) | | `response_format` | No | Format of the response. `mp3`, `wav`, `opus`, and `pcm` are supported. Default: `mp3`| ![Status](https://img.shields.io/badge/partial-yellow) | | `stream_format` | No | If set, the response will be streamed. Only `audio` is supported, which will output `pcm` audio. Default: not set| ![Status](https://img.shields.io/badge/partial-yellow) | #### Example request === "Bash" ```bash curl -X POST http://localhost:8000/api/v1/audio/speech \ -H "Content-Type: application/json" \ -d '{ "model": "kokoro-v1", "input": "Lemonade can speak!", "speed": 1.0, "steps": 4, "response_format": "mp3" }' ``` #### Response format The generated audio file is returned as-is. ### `GET /api/v1/models` ![Status](https://img.shields.io/badge/status-fully_available-green) Returns a list of models available on the server in an OpenAI-compatible format. Each model object includes extended fields like `checkpoint`, `recipe`, `size`, `downloaded`, and `labels`. By default, only models available locally (downloaded) are shown, matching OpenAI API behavior. #### Parameters | Parameter | Required | Description | |-----------|----------|-------------| | `show_all` | No | If set to `true`, returns all models from the catalog including those not yet downloaded. Defaults to `false`. | #### Example request ```bash # Show only downloaded models (OpenAI-compatible) curl http://localhost:8000/api/v1/models # Show all models including not-yet-downloaded (extended usage) curl http://localhost:8000/api/v1/models?show_all=true ``` #### Response format ```json { "object": "list", "data": [ { "id": "Qwen3-0.6B-GGUF", "created": 1744173590, "object": "model", "owned_by": "lemonade", "checkpoint": "unsloth/Qwen3-0.6B-GGUF:Q4_0", "recipe": "llamacpp", "size": 0.38, "downloaded": true, "suggested": true, "labels": ["reasoning"] }, { "id": "Gemma-3-4b-it-GGUF", "created": 1744173590, "object": "model", "owned_by": "lemonade", "checkpoint": "ggml-org/gemma-3-4b-it-GGUF:Q4_K_M", "recipe": "llamacpp", "size": 3.61, "downloaded": true, "suggested": true, "labels": ["hot", "vision"] }, { "id": "SD-Turbo", "created": 1744173590, "object": "model", "owned_by": "lemonade", "checkpoint": "stabilityai/sd-turbo:sd_turbo.safetensors", "recipe": "sd-cpp", "size": 5.2, "downloaded": true, "suggested": true, "labels": ["image"], "image_defaults": { "steps": 4, "cfg_scale": 1.0, "width": 512, "height": 512 } } ] } ``` **Field Descriptions:** - `object` - Type of response object, always `"list"` - `data` - Array of model objects with the following fields: - `id` - Model identifier (used for loading and inference requests) - `created` - Unix timestamp of when the model entry was created - `object` - Type of object, always `"model"` - `owned_by` - Owner of the model, always `"lemonade"` - `checkpoint` - Full checkpoint identifier on Hugging Face - `recipe` - Backend/device recipe used to load the model (e.g., `"ryzenai-llm"`, `"llamacpp"`, `"flm"`) - `size` - Model size in GB (omitted for models without size information) - `downloaded` - Boolean indicating if the model is downloaded and available locally - `suggested` - Boolean indicating if the model is recommended for general use - `labels` - Array of tags describing the model (e.g., `"hot"`, `"reasoning"`, `"vision"`, `"embeddings"`, `"reranking"`, `"coding"`, `"tool-calling"`, `"image"`) - `image_defaults` - (Image models only) Default generation parameters for the model: - `steps` - Number of inference steps (e.g., 4 for turbo models, 20 for standard models) - `cfg_scale` - Classifier-free guidance scale (e.g., 1.0 for turbo models, 7.5 for standard models) - `width` - Default image width in pixels - `height` - Default image height in pixels ### `GET /api/v1/models/{model_id}` ![Status](https://img.shields.io/badge/status-fully_available-green) Retrieve a specific model by its ID. Returns the same model object format as the list endpoint above. #### Parameters | Parameter | Required | Description | |-----------|----------|-------------| | `model_id` | Yes | The ID of the model to retrieve. Must match one of the model IDs from the [models list](https://lemonade-server.ai/models.html). | #### Example request ```bash curl http://localhost:8000/api/v1/models/Qwen3-0.6B-GGUF ``` #### Response format Returns a single model object with the same fields as described in the [models list endpoint](#get-apiv1models) above. ```json { "id": "Qwen3-0.6B-GGUF", "created": 1744173590, "object": "model", "owned_by": "lemonade", "checkpoint": "unsloth/Qwen3-0.6B-GGUF:Q4_0", "recipe": "llamacpp", "size": 0.38, "downloaded": true, "suggested": true, "labels": ["reasoning"], "recipe_options" { "ctx_size": 8192, "llamacpp_args": "--no-mmap", "llamacpp_backend": "rocm" } } ``` #### Error responses If the model is not found, the endpoint returns a 404 error: ```json { "error": { "message": "Model Qwen3-0.6B-GGUF has not been found", "type": "not_found" } } ``` ## Additional Endpoints ### `POST /api/v1/pull` ![Status](https://img.shields.io/badge/status-fully_available-green) Register and install models for use with Lemonade Server. #### Parameters The Lemonade Server built-in model registry has a collection of model names that can be pulled and loaded. The `pull` endpoint can install any registered model, and it can also register-then-install any model available on Hugging Face. **Common Parameters** | Parameter | Required | Description | |-----------|----------|-------------| | `stream` | No | If `true`, returns Server-Sent Events (SSE) with download progress. Defaults to `false`. | **Install a Model that is Already Registered** | Parameter | Required | Description | |-----------|----------|-------------| | `model_name` | Yes | [Lemonade Server model name](https://lemonade-server.ai/models.html) to install. | Example request: ```bash curl -X POST http://localhost:8000/api/v1/pull \ -H "Content-Type: application/json" \ -d '{ "model_name": "Qwen2.5-0.5B-Instruct-CPU" }' ``` Response format: ```json { "status":"success", "message":"Installed model: Qwen2.5-0.5B-Instruct-CPU" } ``` In case of an error, the status will be `error` and the message will contain the error message. **Register and Install a Model** Registration will place an entry for that model in the `user_models.json` file, which is located in the user's Lemonade cache (default: `~/.cache/lemonade`). Then, the model will be installed. Once the model is registered and installed, it will show up in the `models` endpoint alongside the built-in models and can be loaded. The `recipe` field defines which software framework and device will be used to load and run the model. For more information on OGA and Hugging Face recipes, see the [Lemonade API README](../lemonade_api.md). For information on GGUF recipes, see [llamacpp](#gguf-support). > Note: the `model_name` for registering a new model must use the `user` namespace, to prevent collisions with built-in models. For example, `user.Phi-4-Mini-GGUF`. | Parameter | Required | Description | |-----------|----------|-------------| | `model_name` | Yes | Namespaced [Lemonade Server model name](https://lemonade-server.ai/models.html) to register and install. | | `checkpoint` | Yes | HuggingFace checkpoint to install. | | `recipe` | Yes | Lemonade API recipe to load the model with. | | `reasoning` | No | Whether the model is a reasoning model, like DeepSeek (default: false). Adds 'reasoning' label. | | `vision` | No | Whether the model has vision capabilities for processing images (default: false). Adds 'vision' label. | | `embedding` | No | Whether the model is an embedding model (default: false). Adds 'embeddings' label. | | `reranking` | No | Whether the model is a reranking model (default: false). Adds 'reranking' label. | | `mmproj` | No | Multimodal Projector (mmproj) file to use for vision models. | Example request: ```bash curl -X POST http://localhost:8000/api/v1/pull \ -H "Content-Type: application/json" \ -d '{ "model_name": "user.Phi-4-Mini-GGUF", "checkpoint": "unsloth/Phi-4-mini-instruct-GGUF:Q4_K_M", "recipe": "llamacpp" }' ``` Response format: ```json { "status":"success", "message":"Installed model: user.Phi-4-Mini-GGUF" } ``` In case of an error, the status will be `error` and the message will contain the error message. #### Streaming Response (stream=true) When `stream=true`, the endpoint returns Server-Sent Events with real-time download progress: ``` event: progress data: {"file":"model.gguf","file_index":1,"total_files":2,"bytes_downloaded":1073741824,"bytes_total":2684354560,"percent":40} event: progress data: {"file":"config.json","file_index":2,"total_files":2,"bytes_downloaded":1024,"bytes_total":1024,"percent":100} event: complete data: {"file_index":2,"total_files":2,"percent":100} ``` **Event Types:** | Event | Description | |-------|-------------| | `progress` | Sent during download with current file and byte progress | | `complete` | Sent when all files are downloaded successfully | | `error` | Sent if download fails, with `error` field containing the message | ### `POST /api/v1/delete` ![Status](https://img.shields.io/badge/status-fully_available-green) Delete a model by removing it from local storage. If the model is currently loaded, it will be unloaded first. #### Parameters | Parameter | Required | Description | |-----------|----------|-------------| | `model_name` | Yes | [Lemonade Server model name](https://lemonade-server.ai/models.html) to delete. | Example request: ```bash curl -X POST http://localhost:8000/api/v1/delete \ -H "Content-Type: application/json" \ -d '{ "model_name": "Qwen2.5-0.5B-Instruct-CPU" }' ``` Response format: ```json { "status":"success", "message":"Deleted model: Qwen2.5-0.5B-Instruct-CPU" } ``` In case of an error, the status will be `error` and the message will contain the error message. ### `POST /api/v1/load` ![Status](https://img.shields.io/badge/status-fully_available-green) Explicitly load a registered model into memory. This is useful to ensure that the model is loaded before you make a request. Installs the model if necessary. #### Parameters | Parameter | Required | Applies to | Description | |-----------|----------|------------|-------------| | `model_name` | Yes | All | [Lemonade Server model name](https://lemonade-server.ai/models.html) to load. | | `save_options` | No | All | Boolean. If true, saves recipe options to `recipe_options.json`. Any previously stored value for `model_name` is replaced. | | `ctx_size` | No | llamacpp, flm, ryzenai-llm | Context size for the model. Overrides the default value. | | `llamacpp_backend` | No | llamacpp | LlamaCpp backend to use (`vulkan`, `rocm`, `metal` or `cpu`). | | `llamacpp_args` | No | llamacpp | Custom arguments to pass to llama-server. The following are NOT allowed: `-m`, `--port`, `--ctx-size`, `-ngl`, `--jinja`, `--mmproj`, `--embeddings`, `--reranking`. | | `whispercpp_backend` | No | whispercpp | WhisperCpp backend: `npu` or `cpu` on Windows; `cpu` or `vulkan` on Linux. Default is `npu` if supported. | | `steps` | No | sd-cpp | Number of inference steps for image generation. Default: 20. | | `cfg_scale` | No | sd-cpp | Classifier-free guidance scale for image generation. Default: 7.0. | | `width` | No | sd-cpp | Image width in pixels. Default: 512. | | `height` | No | sd-cpp | Image height in pixels. Default: 512. | **Setting Priority:** When loading a model, settings are applied in this priority order: 1. Values explicitly passed in the `load` request (highest priority) 2. Per-model values configurable in `recipe_options.json` (see below for details) 3. Values set via `lemonade-server` CLI arguments or environment variables 4. Default hardcoded values in `lemonade-router` (lowest priority) #### Per-model options You can configure recipe-specific options on a per-model basis. Lemonade manages a file called `recipe_options.json` in the user's Lemonade cache (default: `~/.cache/lemonade`). The available options depend on the model's recipe: ```json { "user.Qwen2.5-Coder-1.5B-Instruct": { "ctx_size": 16384, "llamacpp_backend": "vulkan", "llamacpp_args": "-np 2 -kvu" }, "Qwen3-Coder-30B-A3B-Instruct-GGUF" : { "llamacpp_backend": "rocm" }, "whisper-large-v3-turbo-q8_0.bin": { "whispercpp_backend": "npu" } } ``` Note that model names include any applicable prefix, such as `user.` and `extra.`. #### Example requests Basic load: ```bash curl -X POST http://localhost:8000/api/v1/load \ -H "Content-Type: application/json" \ -d '{ "model_name": "Qwen2.5-0.5B-Instruct-CPU" }' ``` Load with custom settings: ```bash curl -X POST http://localhost:8000/api/v1/load \ -H "Content-Type: application/json" \ -d '{ "model_name": "Qwen3-0.6B-GGUF", "ctx_size": 8192, "llamacpp_backend": "rocm", "llamacpp_args": "--flash-attn on --no-mmap" }' ``` Load and save settings: ```bash curl -X POST http://localhost:8000/api/v1/load \ -H "Content-Type: application/json" \ -d '{ "model_name": "Qwen3-0.6B-GGUF", "ctx_size": 8192, "llamacpp_backend": "vulkan", "llamacpp_args": "--no-context-shift --no-mmap", "save_options": true }' ``` Load a Whisper model with NPU backend: ```bash curl -X POST http://localhost:8000/api/v1/load \ -H "Content-Type: application/json" \ -d '{ "model_name": "whisper-large-v3-turbo-q8_0.bin", "whispercpp_backend": "npu" }' ``` Load an image generation model with custom settings: ```bash curl -X POST http://localhost:8000/api/v1/load \ -H "Content-Type: application/json" \ -d '{ "model_name": "sd-turbo", "steps": 4, "cfg_scale": 1.0, "width": 512, "height": 512 }' ``` #### Response format ```json { "status":"success", "message":"Loaded model: Qwen2.5-0.5B-Instruct-CPU" } ``` In case of an error, the status will be `error` and the message will contain the error message. ### `POST /api/v1/unload` ![Status](https://img.shields.io/badge/status-fully_available-green) Explicitly unload a model from memory. This is useful to free up memory while still leaving the server process running (which takes minimal resources but a few seconds to start). #### Parameters | Parameter | Required | Description | |-----------|----------|-------------| | `model_name` | No | Name of the specific model to unload. If not provided, all loaded models will be unloaded. | #### Example requests Unload a specific model: ```bash curl -X POST http://localhost:8000/api/v1/unload \ -H "Content-Type: application/json" \ -d '{"model_name": "Qwen3-0.6B-GGUF"}' ``` Unload all models: ```bash curl -X POST http://localhost:8000/api/v1/unload ``` #### Response format Success response: ```json { "status": "success", "message": "Model unloaded successfully" } ``` Error response (model not found): ```json { "status": "error", "message": "Model not found: Qwen3-0.6B-GGUF" } ``` In case of an error, the status will be `error` and the message will contain the error message. ### `GET /api/v1/health` ![Status](https://img.shields.io/badge/status-fully_available-green) Check the health of the server. This endpoint returns information about loaded models. #### Parameters This endpoint does not take any parameters. #### Example request ```bash curl http://localhost:8000/api/v1/health ``` #### Response format ```json { "status": "ok", "version":"9.3.3", "websocket_port":9000, "model_loaded": "Llama-3.2-1B-Instruct-Hybrid", "all_models_loaded": [ { "model_name": "Llama-3.2-1B-Instruct-Hybrid", "checkpoint": "amd/Llama-3.2-1B-Instruct-awq-g128-int4-asym-fp16-onnx-hybrid", "last_use": 1732123456.789, "type": "llm", "device": "gpu npu", "recipe": "ryzenai-llm", "recipe_options": { "ctx_size": 4096 }, "backend_url": "http://127.0.0.1:8001/v1" }, { "model_name": "nomic-embed-text-v1-GGUF", "checkpoint": "nomic-ai/nomic-embed-text-v1-GGUF:Q4_K_S", "last_use": 1732123450.123, "type": "embedding", "device": "gpu", "recipe": "llamacpp", "recipe_options": { "ctx_size": 8192, "llamacpp_args": "--no-mmap", "llamacpp_backend": "rocm" }, "backend_url": "http://127.0.0.1:8002/v1" } ], "max_models": { "audio":1, "embedding":1, "image":1, "llm":1, "reranking":1, "tts":1 } } ``` **Field Descriptions:** - `status` - Server health status, always `"ok"` - `version` - Version number of Lemonade Server - `model_loaded` - Model name of the most recently accessed model - `all_models_loaded` - Array of all currently loaded models with details: - `model_name` - Name of the loaded model - `checkpoint` - Full checkpoint identifier - `last_use` - Unix timestamp of last access (load or inference) - `type` - Model type: `"llm"`, `"embedding"`, or `"reranking"` - `device` - Space-separated device list: `"cpu"`, `"gpu"`, `"npu"`, or combinations like `"gpu npu"` - `backend_url` - URL of the backend server process handling this model (useful for debugging) - `recipe`: - Backend/device recipe used to load the model (e.g., `"ryzenai-llm"`, `"llamacpp"`, `"flm"`) - `recipe_options`: - Options used to load the model (e.g., `"ctx_size"`, `"llamacpp_backend"`, `"llamacpp_args"`) - `max_models` - Maximum number of models that can be loaded simultaneously per type (set via `--max-loaded-models`): - `llm` - Maximum LLM/chat models - `embedding` - Maximum embedding models - `reranking` - Maximum reranking models - `audio` - Maximum speech-to-text models - `image` - Maximum image models - `tts` - Maximum text-to-speech models - `websocket_port` - *(optional)* Port of the WebSocket server for the [Realtime Audio Transcription API](#realtime-audio-transcription-api-websocket). Only present when the WebSocket server is running. The port is OS-assigned. ### `GET /api/v1/stats` ![Status](https://img.shields.io/badge/status-fully_available-green) Performance statistics from the last request. #### Parameters This endpoint does not take any parameters. #### Example request ```bash curl http://localhost:8000/api/v1/stats ``` #### Response format ```json { "time_to_first_token": 2.14, "tokens_per_second": 33.33, "input_tokens": 128, "output_tokens": 5, "decode_token_times": [0.01, 0.02, 0.03, 0.04, 0.05], "prompt_tokens": 9 } ``` **Field Descriptions:** - `time_to_first_token` - Time in seconds until the first token was generated - `tokens_per_second` - Generation speed in tokens per second - `input_tokens` - Number of tokens processed - `output_tokens` - Number of tokens generated - `decode_token_times` - Array of time taken for each generated token - `prompt_tokens` - Total prompt tokens including cached tokens ### `GET /api/v1/system-info` ![Status](https://img.shields.io/badge/status-fully_available-green) System information endpoint that provides complete hardware details and device enumeration. #### Example request ```bash curl "http://localhost:8000/api/v1/system-info" ``` #### Response format ```json { "OS Version": "Windows-10-10.0.26100-SP0", "Processor": "AMD Ryzen AI 9 HX 375 w/ Radeon 890M", "Physical Memory": "32.0 GB", "OEM System": "ASUS Zenbook S 16", "BIOS Version": "1.0.0", "CPU Max Clock": "5100 MHz", "Windows Power Setting": "Balanced", "devices": { "cpu": { "name": "AMD Ryzen AI 9 HX 375 w/ Radeon 890M", "cores": 12, "threads": 24, "available": true, "family": "x86_64" }, "amd_igpu": { "name": "AMD Radeon(TM) 890M Graphics", "vram_gb": 0.5, "available": true, "family": "gfx1150" }, "amd_dgpu": [], "amd_npu": { "name": "AMD Ryzen AI 9 HX 375 w/ Radeon 890M", "power_mode": "Default", "available": true, "family": "XDNA2" } }, "recipes": { "llamacpp": { "default_backend": "vulkan", "backends": { "vulkan": { "devices": ["cpu", "amd_igpu"], "state": "installed", "message": "", "action": "", "version": "b7869" }, "rocm": { "devices": ["amd_igpu"], "state": "installable", "message": "Backend is supported but not installed.", "action": "lemonade-server recipes --install llamacpp:rocm" }, "metal": { "devices": [], "state": "unsupported", "message": "Requires macOS", "action": "" }, "cpu": { "devices": ["cpu"], "state": "update_required", "message": "Backend update is required before use.", "action": "lemonade-server recipes --install llamacpp:cpu" } } }, "whispercpp": { "default_backend": "default", "backends": { "default": { "devices": ["cpu"], "state": "installable", "message": "Backend is supported but not installed.", "action": "lemonade-server recipes --install whispercpp:default" } } }, "sd-cpp": { "default_backend": "default", "backends": { "default": { "devices": ["cpu"], "state": "installable", "message": "Backend is supported but not installed.", "action": "lemonade-server recipes --install sd-cpp:default" } } }, "flm": { "default_backend": "default", "backends": { "default": { "devices": ["amd_npu"], "state": "installed", "message": "", "action": "", "version": "1.2.0" } } }, "ryzenai-llm": { "default_backend": "default", "backends": { "default": { "devices": ["amd_npu"], "state": "installed", "message": "", "action": "" } } } } } ``` **Field Descriptions:** - **System fields:** - `OS Version` - Operating system name and version - `Processor` - CPU model name - `Physical Memory` - Total RAM - `OEM System` - System/laptop model name (Windows only) - `BIOS Version` - BIOS information (Windows only) - `CPU Max Clock` - Maximum CPU clock speed (Windows only) - `Windows Power Setting` - Current power plan (Windows only) - `devices` - Hardware devices detected on the system (no software/support information) - `cpu` - CPU information (name, cores, threads) - `amd_igpu` - AMD integrated GPU (if present) - `amd_dgpu` - Array of AMD discrete GPUs (if present) - `nvidia_dgpu` - Array of NVIDIA discrete GPUs (if present) - `amd_npu` - AMD NPU device (if present) - `recipes` - Software recipes and their backend support status - Each recipe (e.g., `llamacpp`, `whispercpp`, `flm`) contains: - `default_backend` - Preferred backend selected by server policy for this system (present when at least one backend is not `unsupported`) - `backends` - Available backends for this recipe - Each backend contains: - `devices` - List of devices **on this system** that support this backend (empty if not supported) - `state` - Backend lifecycle state: `unsupported`, `installable`, `update_required`, or `installed` - `message` - Human-readable status text for GUI and CLI users. Required for `unsupported`, `installable`, and `update_required`; empty for `installed`. - `action` - Actionable user instruction string. For install/update cases this is typically an exact CLI command; for other states it may be empty or another actionable value (for example, a URL). - `version` - Installed or configured backend version (when available) ### `POST /api/v1/install` ![Status](https://img.shields.io/badge/status-fully_available-green) Install or update a backend for a specific recipe/backend pair. If the backend is already installed but outdated, this endpoint updates it to the configured version. #### Parameters | Parameter | Required | Description | |-----------|----------|-------------| | `recipe` | Yes | Recipe name (for example, `llamacpp`, `flm`, `whispercpp`, `sd-cpp`, `ryzenai-llm`) | | `backend` | Yes | Backend name within the recipe (for example, `vulkan`, `rocm`, `cpu`, `default`) | | `stream` | No | If `true`, returns Server-Sent Events with progress. Defaults to `false`. | #### Example request ```bash curl -X POST http://localhost:8000/api/v1/install \ -H "Content-Type: application/json" \ -d '{ "recipe": "llamacpp", "backend": "vulkan", "stream": false }' ``` #### Response format ```json { "status":"success", "recipe":"llamacpp", "backend":"vulkan" } ``` In case of an error, returns an `error` field with details. ### `POST /api/v1/uninstall` ![Status](https://img.shields.io/badge/status-fully_available-green) Uninstall a backend for a specific recipe/backend pair. If loaded models are using that backend, they are unloaded first. #### Parameters | Parameter | Required | Description | |-----------|----------|-------------| | `recipe` | Yes | Recipe name | | `backend` | Yes | Backend name | #### Example request ```bash curl -X POST http://localhost:8000/api/v1/uninstall \ -H "Content-Type: application/json" \ -d '{ "recipe": "llamacpp", "backend": "vulkan" }' ``` #### Response format ```json { "status":"success", "recipe":"llamacpp", "backend":"vulkan" } ``` In case of an error, returns an `error` field with details. # Debugging To help debug the Lemonade server, you can use the `--log-level` parameter to control the verbosity of logging information. The server supports multiple logging levels that provide increasing amounts of detail about server operations. ``` lemonade-server serve --log-level [level] ``` Where `[level]` can be one of: - **critical**: Only critical errors that prevent server operation. - **error**: Error conditions that might allow continued operation. - **warning**: Warning conditions that should be addressed. - **info**: (Default) General informational messages about server operation. - **debug**: Detailed diagnostic information for troubleshooting, including metrics such as input/output token counts, Time To First Token (TTFT), and Tokens Per Second (TPS). - **trace**: Very detailed tracing information, including everything from debug level plus all input prompts. # GGUF Support The `llama-server` backend works with Lemonade's suggested `*-GGUF` models, as well as any .gguf model from Hugging Face. Windows and Ubuntu Linux are supported. Details: - Lemonade Server wraps `llama-server` with support for the `lemonade-server` CLI, client web app, and endpoints (e.g., `models`, `pull`, `load`, etc.). - The `chat/completions`, `completions`, `embeddings`, and `reranking` endpoints are supported. - The `embeddings` endpoint requires embedding-specific models (e.g., nomic-embed-text models). - The `reranking` endpoint requires reranker-specific models (e.g., bge-reranker models). - `responses` is not supported at this time. - A single Lemonade Server process can seamlessly switch between GGUF, ONNX, and FastFlowLM models. - Lemonade Server will attempt to load models onto GPU with Vulkan first, and if that doesn't work it will fall back to CPU. - From the end-user's perspective, OGA vs. GGUF should be completely transparent: they wont be aware of whether the built-in server or `llama-server` is serving their model. ## Installing GGUF Models To install an arbitrary GGUF from Hugging Face, open the Lemonade web app by navigating to http://localhost:8000 in your web browser, click the Model Management tab, and use the Add a Model form. ## Platform Support Matrix | Platform | GPU Acceleration | CPU Architecture | |----------|------------------|------------------| | Windows | ✅ Vulkan, ROCm | ✅ x64 | | Ubuntu | ✅ Vulkan, ROCm | ✅ x64 | | Other Linux | ⚠️* Vulkan | ⚠️* x64 | *Other Linux distributions may work but are not officially supported. # FastFlowLM Support Similar to the [llama-server support](#gguf-support), Lemonade can also route OpenAI API requests to a FastFlowLM `flm serve` backend. The `flm serve` backend works with Lemonade's suggested `*-FLM` models, as well as any model mentioned in `flm list`. Windows is the only supported operating system. Details: - Lemonade Server wraps `flm serve` with support for the `lemonade-server` CLI, client web app, and all Lemonade custom endpoints (e.g., `pull`, `load`, etc.). - OpenAI API endpoints supported: `models`, `chat/completions` (streaming), and `embeddings`. - The `embeddings` endpoint requires embedding-specific models supported by FLM. - A single Lemonade Server process can seamlessly switch between FLM, OGA, and GGUF models. ## Installing FLM Models To install an arbitrary FLM model: 1. `flm list` to view the supported models. 1. Open the Lemonade web app by navigating to http://localhost:8000 in your web browser, click the Model Management tab, and use the Add a Model form. 1. Use the model name from `flm list` as the "checkpoint name" in the Add a Model form and select "flm" as the recipe. lemonade-sdk-lemonade-d88f5d9/docs/update_readme_marketplace.py000066400000000000000000000071041515430344400247520ustar00rootroot00000000000000#!/usr/bin/env python3 """ Script to update README.md with pinned apps from the marketplace. Fetches apps.json and generates icons for pinned apps. """ import json import re import sys from pathlib import Path from urllib.request import urlopen from urllib.error import URLError # Configuration APPS_JSON_URL = ( "https://raw.githubusercontent.com/lemonade-sdk/marketplace/main/apps.json" ) README_PATH = Path(__file__).parent.parent / "README.md" # Markers in README.md START_MARKER = "" END_MARKER = "" def fetch_apps() -> list: """Fetch apps from the marketplace JSON.""" try: with urlopen(APPS_JSON_URL, timeout=10) as response: data = json.loads(response.read().decode("utf-8")) return data.get("apps", []) except URLError as e: print(f"[ERROR] Failed to fetch apps.json: {e}") sys.exit(1) except json.JSONDecodeError as e: print(f"[ERROR] Failed to parse apps.json: {e}") sys.exit(1) def get_pinned_apps(apps: list, limit: int = 10) -> list: """Get pinned apps (already sorted by the marketplace build script).""" # Apps with pinned=true come first in apps.json (sorted by build.py) pinned = [app for app in apps if app.get("pinned", False)] return pinned[:limit] def generate_markdown(apps: list) -> str: """Generate markdown for pinned apps.""" if not apps: return "" # Generate icon links icons = [] for app in apps: name = app.get("name", "Unknown") logo = app.get("logo", "") link = app.get("links", {}).get("guide") or app.get("links", {}).get("app", "#") if logo: icon_html = f'{name}' icons.append(icon_html) # Join icons with spacing icons_html = "  ".join(icons) # Generate the full markdown block markdown = f"""

{icons_html}

View all apps →
Want your app featured here? Just submit a marketplace PR!

""" return markdown.strip() def update_readme(markdown: str) -> bool: """Update README.md with the generated markdown.""" if not README_PATH.exists(): print(f"[ERROR] README.md not found at {README_PATH}") return False content = README_PATH.read_text(encoding="utf-8") # Check if markers exist if START_MARKER not in content or END_MARKER not in content: print("[ERROR] Markers not found in README.md") print(f" Expected: {START_MARKER} ... {END_MARKER}") return False # Replace content between markers pattern = re.compile( rf"{re.escape(START_MARKER)}.*?{re.escape(END_MARKER)}", re.DOTALL ) new_content = pattern.sub(f"{START_MARKER}\n{markdown}\n{END_MARKER}", content) if new_content == content: print("[INFO] No changes needed in README.md") return True README_PATH.write_text(new_content, encoding="utf-8") print(f"[OK] Updated README.md with {len(markdown.split(' lemonade-sdk-lemonade-d88f5d9/examples/000077500000000000000000000000001515430344400201155ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/examples/README.md000066400000000000000000000027321515430344400214000ustar00rootroot00000000000000# Lemonade Examples Interactive demos that work with Lemonade Server. ## Available Demos ### Audio Transcription - **realtime_transcription.py**: Stream microphone audio for real-time transcription (+ test mode for WAV files) ### LLM Demos - **llm-debate.html**: A debate arena where multiple LLMs can debate each other on any topic - **multi-model-tester.html**: Test prompts across multiple models side-by-side ### Image Generation - **api_image_generation.py**: Generate images using the `images/generations` endpoint ### Speech Synthesis (TTS) - **api_text_to_speech.py**: Generate speech from a prompt using `audio/speech` endpoint ## Setup 1. Install Lemonade Server from the [latest release](https://github.com/lemonade-sdk/lemonade/releases/latest) 2. Start the server: `lemonade-server serve` 3. Pull a model if needed (e.g., `lemonade-server-dev pull Whisper-Tiny`) ## Running the Examples ### Realtime Transcription (Python) Uses the OpenAI-compatible WebSocket API for real-time speech-to-text. ```bash # Install dependencies pip install openai websockets pyaudio # Stream from microphone python realtime_transcription.py # Use a different model python realtime_transcription.py --model Whisper-Small ``` ### LLM Demos Open the HTML files directly in your browser. See [debate-arena.md](debate-arena.md) for detailed instructions on the debate demo. lemonade-sdk-lemonade-d88f5d9/examples/api_image_edits.py000066400000000000000000000150221515430344400235720ustar00rootroot00000000000000""" This example demonstrates how to use the lemonade server API to edit images using Stable Diffusion models via the OpenAI Python client. Prerequisites: 1. Install the OpenAI client: pip install openai pillow 2. Start the lemonade server with SD backend: lemonade-server --sdcpp rocm (or --sdcpp cpu) 3. An image editing model will be auto-downloaded on first use 4. You need a source image to edit (example creates a simple one) Usage: python api_image_edits.py python api_image_edits.py --backend rocm python api_image_edits.py --backend cpu python api_image_edits.py --image path/to/your/image.png """ import base64 import argparse from pathlib import Path from io import BytesIO def create_sample_image(): """Create a simple sample image for testing if none provided.""" try: from PIL import Image, ImageDraw except ImportError: print("Pillow not installed. Install with: pip install pillow") return None # Create a 512x512 white image with a simple shape img = Image.new("RGB", (512, 512), color="white") draw = ImageDraw.Draw(img) # Draw a simple landscape: green ground, blue sky, yellow sun draw.rectangle([(0, 256), (512, 512)], fill="green") # Ground draw.rectangle([(0, 0), (512, 256)], fill="lightblue") # Sky draw.ellipse([(400, 50), (480, 130)], fill="yellow") # Sun output_path = Path("sample_image.png") img.save(output_path) print(f"Created sample image: {output_path.absolute()}") return output_path def edit_image_with_openai_client(image_path, backend="cpu"): """Edit an image using the OpenAI Python client.""" try: from openai import OpenAI except ImportError: print("OpenAI client not installed. Install with: pip install openai") return None # Point to local lemonade server client = OpenAI( base_url="http://localhost:8000/api/v1", api_key="not-needed", # Lemonade doesn't require API key ) print(f"Editing image with OpenAI client (backend: {backend})...") if backend == "cpu": print("(This may take several minutes with CPU backend)") # Read the image file with open(image_path, "rb") as image_file: response = client.images.edit( model="Flux-2-Klein-4B", # or another editing model image=image_file, prompt="Add a red barn and mountains in the background, photorealistic", size="512x512", n=1, ) # Save the edited image if response.data: image_data = base64.b64decode(response.data[0].b64_json) output_path = Path("edited_image_openai.png") output_path.write_bytes(image_data) print(f"Edited image saved to: {output_path.absolute()}") return output_path return None def edit_image_with_requests(image_path, backend="cpu"): """Edit an image using the requests library with multipart form data.""" try: import requests except ImportError: print("Requests not installed. Install with: pip install requests") return None print(f"Editing image with requests library (backend: {backend})...") if backend == "cpu": print("(This may take several minutes with CPU backend)") # Prepare the multipart form data with open(image_path, "rb") as image_file: files = { "image": ("image.png", image_file, "image/png"), } data = { "model": "SD-Turbo", "prompt": "Add a red barn and mountains in the background, photorealistic", "size": "512x512", "n": "1", "response_format": "b64_json", } response = requests.post( "http://localhost:8000/api/v1/images/edits", files=files, data=data, timeout=600, # 10 minutes for image generation ) if response.status_code == 200: result = response.json() if "data" in result and len(result["data"]) > 0: image_data = base64.b64decode(result["data"][0]["b64_json"]) output_path = Path("edited_image_requests.png") output_path.write_bytes(image_data) print(f"Edited image saved to: {output_path.absolute()}") return output_path else: print(f"Unexpected response format: {result}") else: print(f"Error: {response.status_code}") print(response.text) return None if __name__ == "__main__": parser = argparse.ArgumentParser( description="Edit images using Lemonade server with Stable Diffusion" ) parser.add_argument( "--backend", type=str, choices=["cpu", "rocm"], default="cpu", help="Backend to use for image editing (default: cpu). Use 'rocm' for AMD GPU acceleration.", ) parser.add_argument( "--image", type=str, help="Path to the image to edit. If not provided, a sample image will be created.", ) parser.add_argument( "--method", type=str, choices=["openai", "requests", "both"], default="both", help="Which method to use for API calls (default: both)", ) args = parser.parse_args() print("=" * 60) print("Lemonade Image Editing Example") print("=" * 60) print() print("Make sure the lemonade server is running with SD backend:") print(" lemonade-server --sdcpp rocm") print() # Get or create an image if args.image: image_path = Path(args.image) if not image_path.exists(): print(f"Error: Image file not found: {image_path}") exit(1) else: image_path = create_sample_image() if not image_path: exit(1) print() # Try editing with different methods results = [] if args.method in ["openai", "both"]: print("--- Using OpenAI Client ---") result = edit_image_with_openai_client(image_path, args.backend) if result: results.append(result) print() if args.method in ["requests", "both"]: print("--- Using Requests Library ---") result = edit_image_with_requests(image_path, args.backend) if result: results.append(result) print() print("=" * 60) print("Done!") if results: print("Generated images:") for result in results: print(f" - {result}") print() print("Note: The actual editing capabilities depend on the sd-cpp backend") print("and the loaded model. Some models may not support image editing yet.") lemonade-sdk-lemonade-d88f5d9/examples/api_image_generation.py000066400000000000000000000052251515430344400246210ustar00rootroot00000000000000""" This example demonstrates how to use the lemonade server API to generate images using Stable Diffusion models via the OpenAI Python client. Prerequisites: 1. Install the OpenAI client: pip install openai 2. Start the lemonade server: lemonade-server --sdcpp rocm (or --sdcpp cpu) 3. The SD-Turbo model will be auto-downloaded on first use Usage: python api_image_generation.py python api_image_generation.py --backend rocm python api_image_generation.py --backend cpu """ import base64 import argparse from pathlib import Path def generate_with_openai_client(backend="cpu"): """Generate image using the OpenAI Python client.""" try: from openai import OpenAI except ImportError: print("OpenAI client not installed. Install with: pip install openai") return None # Point to local lemonade server client = OpenAI( base_url="http://localhost:8000/api/v1", api_key="not-needed", # Lemonade doesn't require API key ) print(f"Generating image with OpenAI client...{backend}") if backend == "cpu": print("(This may take several minutes with CPU backend)") response = client.images.generate( model="SD-Turbo", prompt="A serene mountain landscape at sunset, digital art", size="512x512", n=1, response_format="b64_json", # SD-specific parameters (passed through) extra_body={ "steps": 4, # SD-Turbo works well with 4 steps "cfg_scale": 1.0, # SD-Turbo uses low CFG }, ) # Save the image if response.data: image_data = base64.b64decode(response.data[0].b64_json) output_path = Path("generated_image_openai.png") output_path.write_bytes(image_data) print(f"Image saved to: {output_path.absolute()}") return output_path return None if __name__ == "__main__": parser = argparse.ArgumentParser( description="Generate images using Lemonade server with Stable Diffusion" ) parser.add_argument( "--backend", type=str, choices=["cpu", "rocm"], default="cpu", help="Backend to use for image generation (default: cpu). Use 'rocm' for AMD GPU acceleration.", ) args = parser.parse_args() print("=" * 60) print("Lemonade Image Generation Example") print("=" * 60) print() print("Make sure the lemonade server is running:") print(" lemonade-server") print() # Generate using OpenAI client result = generate_with_openai_client(args.backend) print() print("=" * 60) print("Done!") if result: print(f"Generated image saved to: {result}") lemonade-sdk-lemonade-d88f5d9/examples/api_image_variations.py000066400000000000000000000165451515430344400246540ustar00rootroot00000000000000""" This example demonstrates how to use the lemonade server API to create variations of images using Stable Diffusion models via the OpenAI Python client. Prerequisites: 1. Install the OpenAI client: pip install openai pillow 2. Start the lemonade server with SD backend: lemonade-server --sdcpp rocm (or --sdcpp cpu) 3. An image model will be auto-downloaded on first use 4. You need a source image (example creates a simple one) Usage: python api_image_variations.py python api_image_variations.py --backend rocm python api_image_variations.py --backend cpu python api_image_variations.py --image path/to/your/image.png python api_image_variations.py --num-variations 3 """ import base64 import argparse from pathlib import Path from io import BytesIO def create_sample_image(): """Create a simple sample image for testing if none provided.""" try: from PIL import Image, ImageDraw except ImportError: print("Pillow not installed. Install with: pip install pillow") return None # Create a 512x512 image with a simple pattern img = Image.new("RGB", (512, 512), color="white") draw = ImageDraw.Draw(img) # Draw a simple scene draw.rectangle([(0, 256), (512, 512)], fill="lightgreen") # Ground draw.rectangle([(0, 0), (512, 256)], fill="skyblue") # Sky draw.ellipse([(350, 50), (450, 150)], fill="gold") # Sun draw.rectangle([(200, 180), (300, 300)], fill="brown") # Tree trunk draw.ellipse([(150, 80), (350, 220)], fill="darkgreen") # Tree foliage output_path = Path("sample_image_variations.png") img.save(output_path) print(f"Created sample image: {output_path.absolute()}") return output_path def create_variations_with_openai_client(image_path, num_variations=1, backend="cpu"): """Create image variations using the OpenAI Python client.""" try: from openai import OpenAI except ImportError: print("OpenAI client not installed. Install with: pip install openai") return [] # Point to local lemonade server client = OpenAI( base_url="http://localhost:8000/api/v1", api_key="not-needed", # Lemonade doesn't require API key ) print( f"Creating {num_variations} variation(s) with OpenAI client (backend: {backend})..." ) if backend == "cpu": print("(This may take several minutes with CPU backend)") results = [] # Read the image file with open(image_path, "rb") as image_file: try: response = client.images.create_variation( model="SD-Turbo", image=image_file, size="512x512", n=num_variations, response_format="b64_json", ) except Exception as e: print(f"Error: {e}") return results # Save the variations if response.data: for i, image_obj in enumerate(response.data): image_data = base64.b64decode(image_obj.b64_json) output_path = Path(f"variation_openai_{i+1}.png") output_path.write_bytes(image_data) print(f"Variation {i+1} saved to: {output_path.absolute()}") results.append(output_path) return results def create_variations_with_requests(image_path, num_variations=1, backend="cpu"): """Create image variations using the requests library with multipart form data.""" try: import requests except ImportError: print("Requests not installed. Install with: pip install requests") return [] print( f"Creating {num_variations} variation(s) with requests library (backend: {backend})..." ) if backend == "cpu": print("(This may take several minutes with CPU backend)") results = [] # Prepare the multipart form data with open(image_path, "rb") as image_file: files = { "image": ("image.png", image_file, "image/png"), } data = { "model": "SD-Turbo", "size": "512x512", "n": str(num_variations), "response_format": "b64_json", } response = requests.post( "http://localhost:8000/api/v1/images/variations", files=files, data=data, timeout=600, # 10 minutes for image generation ) if response.status_code == 200: result = response.json() if "data" in result and len(result["data"]) > 0: for i, image_obj in enumerate(result["data"]): image_data = base64.b64decode(image_obj["b64_json"]) output_path = Path(f"variation_requests_{i+1}.png") output_path.write_bytes(image_data) print(f"Variation {i+1} saved to: {output_path.absolute()}") results.append(output_path) else: print(f"Unexpected response format: {result}") else: print(f"Error: {response.status_code}") print(response.text) return results if __name__ == "__main__": parser = argparse.ArgumentParser( description="Create image variations using Lemonade server with Stable Diffusion" ) parser.add_argument( "--backend", type=str, choices=["cpu", "rocm"], default="cpu", help="Backend to use for image variations (default: cpu). Use 'rocm' for AMD GPU acceleration.", ) parser.add_argument( "--image", type=str, help="Path to the source image. If not provided, a sample image will be created.", ) parser.add_argument( "--num-variations", type=int, default=1, help="Number of variations to generate (default: 1)", ) parser.add_argument( "--method", type=str, choices=["openai", "requests", "both"], default="both", help="Which method to use for API calls (default: both)", ) args = parser.parse_args() print("=" * 60) print("Lemonade Image Variations Example") print("=" * 60) print() print("Make sure the lemonade server is running with SD backend:") print(" lemonade-server --sdcpp rocm") print() # Get or create an image if args.image: image_path = Path(args.image) if not image_path.exists(): print(f"Error: Image file not found: {image_path}") exit(1) else: image_path = create_sample_image() if not image_path: exit(1) print() # Try creating variations with different methods all_results = [] if args.method in ["openai", "both"]: print("--- Using OpenAI Client ---") results = create_variations_with_openai_client( image_path, args.num_variations, args.backend ) all_results.extend(results) print() if args.method in ["requests", "both"]: print("--- Using Requests Library ---") results = create_variations_with_requests( image_path, args.num_variations, args.backend ) all_results.extend(results) print() print("=" * 60) print("Done!") if all_results: print(f"Generated {len(all_results)} variation(s):") for result in all_results: print(f" - {result}") print() print("Note: The actual variation capabilities depend on the sd-cpp backend") print("and the loaded model. Some models may not support image variations yet.") lemonade-sdk-lemonade-d88f5d9/examples/api_text_to_speech.py000066400000000000000000000032271515430344400243410ustar00rootroot00000000000000""" This example demonstrates how to use the lemonade server API to generate speech using Kokoro via the OpenAI Python client. Prerequisites: 1. Install the OpenAI client: pip install openai openai[voice_helpers] 2. Start the lemonade server: lemonade-server 3. The kokoro-v1 model will be auto-downloaded on first use Usage: python api_text_to_speech.py """ import base64 import asyncio from pathlib import Path async def generate_with_openai_client(): """Generate image using the OpenAI Python client.""" try: from openai import AsyncOpenAI from openai.helpers import LocalAudioPlayer except ImportError: print("OpenAI client not installed. Install with: pip install openai") return None # Point to local lemonade server client = AsyncOpenAI( base_url="http://localhost:8000/api/v1", api_key="not-needed", # Lemonade doesn't require API key by default ) print("Generating speech with OpenAI client...") print("(This may take several seconds)") async with client.audio.speech.with_streaming_response.create( model="kokoro-v1", voice="coral", input="Today is a wonderful day to build something people love!", stream_format="audio", ) as response: await LocalAudioPlayer().play(response) if __name__ == "__main__": print("=" * 60) print("Lemonade Text to Speech Example") print("=" * 60) print() print("Make sure the lemonade server is running:") print(" lemonade-server") print() # Generate using OpenAI client asyncio.run(generate_with_openai_client()) print() print("=" * 60) print("Done!") lemonade-sdk-lemonade-d88f5d9/examples/debate-arena.md000066400000000000000000000024511515430344400227510ustar00rootroot00000000000000## Debate Arena Debate Arena is a single-file HTML/CSS/JS web app that pits up to 9 LLMs against each other to answer life's most important yes/no questions. ### Quick start instructions 1. Install Lemonade Server v9.0.8 or higher: https://github.com/lemonade-sdk/lemonade/releases 2. In a terminal: `lemonade-server serve --max-loaded-models 9` 3. Download https://github.com/lemonade-sdk/lemonade/blob/main/examples/llm-debate.html and open it in your web browser 4. You can uncheck some models to save VRAM. Running all 9 requires ~32 GB. ### How it works - All LLMs run at the same time, and the responses are streamed live to the UI. - Web app connects to Lemonade at `http://localhost:8000` - Uses the `pull` endpoint to download the models - Uses the `load` endpoint to start 9 `llama-server` background processes, one each for Qwen, Jan, LFM, etc. - The user’s prompt is sent to all 9 LLMs simultaneously for a hot take by starting 9 streaming `chat/completions` requests with Lemonade’s base URL. - The request with `model=Qwen...` gets routed to Qwen's `llama-server`, and so on. - The 9 LLM responses go into a shared chat history, which is then sent back to the LLMs for reactions in debate phase 2. - Process repeats until all 9 have voted, with escalating system prompts in each phase. lemonade-sdk-lemonade-d88f5d9/examples/llm-debate.html000066400000000000000000002040101515430344400230060ustar00rootroot00000000000000 🍋 Lemonade Debate Arena v2
0
0
0
0 0 0 0 0

🍋 DEBATE ARENA v2

Connecting to server...
lemonade-sdk-lemonade-d88f5d9/examples/migrate-to-systemd.sh000077500000000000000000000111751515430344400242170ustar00rootroot00000000000000#!/bin/bash set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Check if running as root if [[ $EUID -ne 0 ]]; then echo -e "${RED}Error: This script must be run as root${NC}" echo "Please run with: sudo $0" exit 1 fi echo "Lemonade Migration Script" echo "=========================" echo "This script migrates user data from a user account to the lemonade systemd service user." echo "" # Prompt for source user read -p "Enter the username to migrate data from: " SOURCE_USER # Validate source user exists if ! id "$SOURCE_USER" &>/dev/null; then echo -e "${RED}Error: User '$SOURCE_USER' does not exist${NC}" exit 1 fi # Get source user's home directory SOURCE_HOME=$(getent passwd "$SOURCE_USER" | cut -d: -f6) if [[ -z "$SOURCE_HOME" ]]; then echo -e "${RED}Error: Could not determine home directory for user '$SOURCE_USER'${NC}" exit 1 fi if [[ ! -d "$SOURCE_HOME" ]]; then echo -e "${RED}Error: Home directory '$SOURCE_HOME' does not exist${NC}" exit 1 fi # Check if lemonade user exists if ! id "lemonade" &>/dev/null; then echo -e "${RED}Error: 'lemonade' user does not exist${NC}" echo "The systemd service user must be created before running this migration." exit 1 fi # Get lemonade user's home directory LEMONADE_HOME=$(getent passwd "lemonade" | cut -d: -f6) if [[ -z "$LEMONADE_HOME" ]]; then echo -e "${RED}Error: Could not determine home directory for user 'lemonade'${NC}" exit 1 fi if [[ ! -d "$LEMONADE_HOME" ]]; then echo -e "${RED}Error: Lemonade home directory '$LEMONADE_HOME' does not exist${NC}" exit 1 fi # Define paths to migrate declare -a SOURCE_PATHS=( "$SOURCE_HOME/.cache/lemonade" "$SOURCE_HOME/.cache/huggingface" "$SOURCE_HOME/.local/share/lemonade-server" ) declare -a DEST_PATHS=( "$LEMONADE_HOME/.cache/lemonade" "$LEMONADE_HOME/.cache/huggingface" "$LEMONADE_HOME/.local/share/lemonade-server" ) # Check which files exist echo "" echo "Scanning for files to migrate..." echo "" declare -a FILES_TO_MIGRATE=() declare -a DEST_FILES=() for i in "${!SOURCE_PATHS[@]}"; do SOURCE_PATH="${SOURCE_PATHS[$i]}" DEST_PATH="${DEST_PATHS[$i]}" if [[ -e "$SOURCE_PATH" ]]; then # Calculate size SIZE=$(du -sh "$SOURCE_PATH" 2>/dev/null | cut -f1) echo -e "${GREEN}Found:${NC} $SOURCE_PATH (${SIZE})" echo -e " -> Will migrate to: $DEST_PATH" FILES_TO_MIGRATE+=("$SOURCE_PATH") DEST_FILES+=("$DEST_PATH") fi done if [[ ${#FILES_TO_MIGRATE[@]} -eq 0 ]]; then echo -e "${YELLOW}No files found to migrate.${NC}" echo "The user may not have any lemonade data, or it may be in a custom location." exit 0 fi echo "" echo "Summary:" echo "--------" echo "Source user: $SOURCE_USER ($SOURCE_HOME)" echo "Destination user: lemonade ($LEMONADE_HOME)" echo "Files/directories to migrate: ${#FILES_TO_MIGRATE[@]}" echo "" # Confirm before proceeding read -p "Do you want to proceed with the migration? (yes/no): " CONFIRM if [[ "$CONFIRM" != "yes" ]]; then echo "Migration cancelled." exit 0 fi echo "" echo "Starting migration..." # Perform migration for i in "${!FILES_TO_MIGRATE[@]}"; do SOURCE_PATH="${FILES_TO_MIGRATE[$i]}" DEST_PATH="${DEST_FILES[$i]}" echo "" echo -e "${GREEN}Migrating:${NC} $SOURCE_PATH" echo -e " to: $DEST_PATH" # Create parent directory if needed PARENT_DIR=$(dirname "$DEST_PATH") if [[ ! -d "$PARENT_DIR" ]]; then echo " Creating parent directory: $PARENT_DIR" mkdir -p "$PARENT_DIR" chown lemonade:lemonade "$PARENT_DIR" fi # Check if destination already exists if [[ -e "$DEST_PATH" ]]; then echo -e " ${YELLOW}Warning: Destination already exists - merging files${NC}" # Use rsync to merge directories (move source files into destination) echo " Merging into existing destination..." rsync -av --remove-source-files "$SOURCE_PATH/" "$DEST_PATH/" else # Move files (preserves source structure) echo " Moving files..." mv "$SOURCE_PATH" "$DEST_PATH" fi # Update ownership echo " Updating ownership to lemonade:lemonade..." chown -R lemonade:lemonade "$DEST_PATH" echo -e " ${GREEN}Done${NC}" done echo "" echo -e "${GREEN}Migration completed successfully!${NC}" echo "" echo "Next steps:" echo "1. Verify the migrated files in $LEMONADE_HOME" echo "2. (Re)start the lemonade systemd service: systemctl restart lemonade-server" echo "3. Check service status: systemctl status lemonade-server" echo "4. Optionally, remove old files from $SOURCE_HOME to free up space" echo "" lemonade-sdk-lemonade-d88f5d9/examples/multi-model-tester.html000066400000000000000000001756071515430344400245570ustar00rootroot00000000000000 Lemonade Multi-Model Tester

🍋 Lemonade Multi-Model Tester

Test multiple models concurrently with streaming responses

Disconnected
🎯

No Models Loaded

Click on a model from the sidebar to load it

lemonade-sdk-lemonade-d88f5d9/examples/realtime_transcription.py000066400000000000000000000213021515430344400252460ustar00rootroot00000000000000""" Realtime Audio Transcription with OpenAI SDK Uses the official OpenAI SDK to connect to Lemonade Server's OpenAI-compatible realtime transcription endpoint. Requirements: pip install openai pyaudio websockets Usage: python realtime_transcription.py python realtime_transcription.py --model Whisper-Small """ import argparse import asyncio import base64 import struct import sys import os # Enable ANSI escape codes on Windows if os.name == 'nt': try: import ctypes kernel32 = ctypes.windll.kernel32 # Enable ENABLE_VIRTUAL_TERMINAL_PROCESSING kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7) except: pass # Check dependencies try: from openai import AsyncOpenAI except ImportError: print("Error: openai library not found.") print("Install it with: pip install openai") sys.exit(1) try: import pyaudio except ImportError: print("Error: pyaudio library not found.") print("Install it with: pip install pyaudio") sys.exit(1) try: import websockets # noqa: F401 — required by openai SDK for realtime except ImportError: print("Error: websockets library not found.") print("Install it with: pip install websockets") sys.exit(1) TARGET_RATE = 16000 # Whisper expects 16kHz mono PCM16 CHUNK_SIZE = 4096 # Samples per read at native rate (~85ms at 48kHz) def downsample_to_16k(pcm16_bytes, native_rate): """Downsample PCM16 audio from native_rate to 16kHz using linear interpolation. Matches the resampling approach used in the Electron app's useAudioCapture hook. """ if native_rate == TARGET_RATE: return pcm16_bytes n_samples = len(pcm16_bytes) // 2 samples = struct.unpack(f'<{n_samples}h', pcm16_bytes) ratio = native_rate / TARGET_RATE output_length = int(n_samples / ratio) output = bytearray(output_length * 2) for i in range(output_length): src_idx = i * ratio idx_floor = int(src_idx) idx_ceil = min(idx_floor + 1, n_samples - 1) frac = src_idx - idx_floor sample = samples[idx_floor] * (1 - frac) + samples[idx_ceil] * frac clamped = max(-32768, min(32767, int(sample))) struct.pack_into(' term_width - 4: delta_text = "..." + delta_text[-(term_width - 4):] print(f"\r\033[2K{delta_text}", end="", flush=True) elif event.type == "conversation.item.input_audio_transcription.completed": transcript = getattr(event, "transcript", "").replace('\n', ' ').strip() if transcript: transcripts.append(transcript) # Clear interim line, print final on its own line print(f"\r\033[2K{transcript}") elif event.type == "error": error = getattr(event, "error", None) msg = getattr(error, "message", "Unknown") if error else "Unknown" print(f"\nError: {msg}") except asyncio.CancelledError: pass send_task = asyncio.create_task(send_audio()) recv_task = asyncio.create_task(receive_messages()) try: await asyncio.gather(send_task, recv_task) except KeyboardInterrupt: print("\n\nStopping...") send_task.cancel() recv_task.cancel() # Commit remaining audio await conn.input_audio_buffer.commit() # Wait for final transcript try: while True: event = await asyncio.wait_for(conn.recv(), timeout=3) if event.type == "conversation.item.input_audio_transcription.completed": transcript = getattr(event, "transcript", "").strip() if transcript: transcripts.append(transcript) print(f">>> {transcript}") break except: pass finally: stream.stop_stream() stream.close() pa.terminate() if transcripts: print("\n" + "-" * 40) print("Full transcript:") print(" ".join(transcripts)) asyncio.run(run()) def main(): parser = argparse.ArgumentParser( description="Realtime transcription using OpenAI-compatible API" ) parser.add_argument( "--model", default="Whisper-Tiny", help="Whisper model (default: Whisper-Tiny)" ) parser.add_argument( "--server", default="http://localhost:8000/api/v1", help="REST API URL" ) args = parser.parse_args() transcribe_microphone(args.model, args.server) if __name__ == "__main__": main() lemonade-sdk-lemonade-d88f5d9/mkdocs.yml000066400000000000000000000077251515430344400203150ustar00rootroot00000000000000# This is the configuration file for MkDocs, a static site generator that uses Markdown files to create documentation sites. # The configuration file is written in YAML format and contains various settings for the site. # To install the MkDocs dependencies, run the following command in the terminal: # pip install -r docs/assets/mkdocs_requirements.txt # To build the site, run the following command in the terminal: # mkdocs build # To serve the site locally, run the following command in the terminal: # mkdocs serve # To deploy the site to GitHub Pages, run the following command in the terminal: # mkdocs gh-deploy <-- this should be updated when we have CI setup with what the real instructions are. site_name: Lemonade Server Documentation site_url: https://lemonade-server.ai/ site_description: Lemonade Server is a lightweight, open-source local LLM server that allows you to run and manage multiple AI applications on your local machine. It provides a simple CLI for managing applications and supports various LLMs, making it easy to deploy and use AI models locally. edit_uri: server/README.md repo_name: lemonade-sdk/lemonade repo_url: https://github.com/lemonade-sdk/lemonade plugins: - monorepo - search theme: name: material logo: assets/logo.png # If we want to use a custom logo instead of an icon icon: repo: fontawesome/brands/github # This is the icon for the repo link in the header favicon: assets/favicon.ico features: - navigation.footer - navigation.tracking - navigation.expand - navigation.top - content.code.annotate - content.code.copy palette: # Light mode settings - scheme: lightmode primary: amber toggle: icon: material/weather-night name: Switch to dark mode # Dark mode settings - scheme: slate primary: amber accent: amber toggle: icon: material/weather-sunny name: Switch to light mode nav_style: dark # Add the list of markdown files to be included in the documentation # The order of the files in the list will determine the order they appear in the documentation nav: - Downloading and Getting Started: server/README.md - Developer Getting Started: dev-getting-started.md - Supported Applications: server/apps/README.md - Application Guides: - AI Dev Gallery: server/apps/ai-dev-gallery.md - AI Toolkit: server/apps/ai-toolkit.md - AnythingLLM: server/apps/anythingLLM.md - CodeGPT: server/apps/codeGPT.md - Continue: server/apps/continue.md - Mindcraft: server/apps/mindcraft.md - OpenHands: server/apps/open-hands.md - Open WebUI: server/apps/open-webui.md - Wut: server/apps/wut.md - Lemonade Server CLI Guide: server/lemonade-server-cli.md - Understanding local LLM servers: server/concepts.md - Server Spec: server/server_spec.md - Integration Guide: server/server_integration.md - Custom Model Configuration: server/custom-models.md - FAQ Guide: faq.md - Contribution Guide: contribute.md not_in_nav: | /index.md /self_hosted_runners.md /eval/finetuned_model_export.md exclude_docs: | code.md versioning.md eval/README.md eval/humaneval_accuracy.md eval/mmlu_accuracy.md eval/perplexity.md eval/quark.md eval/ort_genai_igpu.md eval/lm-eval.md # The following adds icons on the bottom of the page extra: homepage: https://lemonade-server.ai social: - icon: simple/youtube link: https://www.youtube.com/@AMDDevCentral - icon: simple/github link: https://github.com/lemonade-sdk/lemonade - icon: simple/discord link: https://discord.gg/5xXzkMu8Zk copyright: Copyright © 2025 AMD. All rights reserved. # The custom CSS for colors and more extra_css: - assets/extra.css # The custom JavaScript for the carousel for the videos extra_javascript: - assets/carousel.js markdown_extensions: - admonition - pymdownx.superfences # Better code blocks - pymdownx.tabbed: # Tabbed code blocks alternate_style: true lemonade-sdk-lemonade-d88f5d9/setup.ps1000066400000000000000000000114161515430344400200670ustar00rootroot00000000000000#!/usr/bin/env pwsh # Lemonade development environment setup script for Windows # This script prepares the development environment for building Lemonade param() $ErrorActionPreference = "Stop" # Colors for output $Info = "Blue" $Success = "Green" $Warning = "Yellow" $Error = "Red" # Helper functions function Write-Info { param([string]$Message) Write-Host "[INFO] $Message" -ForegroundColor $Info } function Write-Success { param([string]$Message) Write-Host "[SUCCESS] $Message" -ForegroundColor $Success } function Write-Warning { param([string]$Message) Write-Host "[WARNING] $Message" -ForegroundColor $Warning } function Write-Error-Custom { param([string]$Message) Write-Host "[ERROR] $Message" -ForegroundColor $Error } # Check if command exists function Command-Exists { param([string]$Command) try { if (Get-Command $Command -ErrorAction Stop) { return $true } } catch { return $false } } Write-Info "Lemonade Development Setup" Write-Info "Operating System: Windows" Write-Host "" # Check and install pre-commit Write-Info "Checking pre-commit installation..." if (-not (Command-Exists "pre-commit")) { Write-Warning "pre-commit not found, installing..." if (Command-Exists "pip") { pip install pre-commit } elseif (Command-Exists "pip3") { pip3 install pre-commit } elseif (Command-Exists "py") { Write-Warning "Pip or Pip3 not found. Installing using py." py -m pip install pre-commit Write-Warning "If you encounter issues, please ensure the Python Scripts directory is in your PATH." } else { Write-Error-Custom "Neither pip nor pip3 found. Please install Python 3 first." exit 1 } Write-Success "pre-commit installed" } else { Write-Success "pre-commit is already installed" } # Install pre-commit hooks if (Test-Path ".pre-commit-config.yaml") { Write-Info "Installing pre-commit hooks..." pre-commit install Write-Success "pre-commit hooks installed" } else { Write-Warning "No .pre-commit-config.yaml found, skipping hook installation" } Write-Host "" # Step 3: Check and install Node.js and npm Write-Info "Step 3: Checking Node.js and npm installation..." if (-not (Command-Exists "node")) { Write-Error-Custom "Node.js not found" Write-Info "Please install Node.js from https://nodejs.org/" Write-Info "You can also use Chocolatey if installed: choco install nodejs" exit 1 } else { Write-Success "Node.js is installed" } if (-not (Command-Exists "npm")) { Write-Error-Custom "npm is not available" Write-Info "Please reinstall Node.js or ensure npm is in your PATH" exit 1 } else { Write-Success "npm is installed" } Write-Host "" # Check and install Node.js and npm Write-Info "Checking Node.js and npm installation..." if (-not (Command-Exists "node")) { Write-Error-Custom "Node.js not found" Write-Info "Please install Node.js from https://nodejs.org/" Write-Info "You can also use Chocolatey if installed: choco install nodejs" exit 1 } else { Write-Success "Node.js is installed" } if (-not (Command-Exists "npm")) { Write-Error-Custom "npm is not available" Write-Info "Please reinstall Node.js or ensure npm is in your PATH" exit 1 } else { Write-Success "npm is installed" } Write-Host "" # Clean and create build directory Write-Info "Preparing build directory..." if (Test-Path "build") { Write-Warning "Removing existing build directory..." Remove-Item -Recurse -Force "build" } New-Item -ItemType Directory -Path "build" -Force | Out-Null Write-Success "Build directory created" Write-Host "" # Detect Visual Studio version and select CMake preset $vswhereExe = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" $cmakePreset = "windows" if (Test-Path $vswhereExe) { $vsMajor = (& $vswhereExe -latest -property catalog_productLineVersion) if ($vsMajor -eq "18") { $cmakePreset = "vs18" } Write-Info "Detected Visual Studio v$vsMajor, using preset: $cmakePreset" } else { Write-Warning "vswhere not found, defaulting to preset: windows" } cmake --preset $cmakePreset if ($LASTEXITCODE -ne 0) { Write-Error-Custom "CMake configuration failed" exit 1 } Write-Success "CMake configured successfully" Write-Host "" Write-Host "==========================================" -ForegroundColor Green Write-Success "Setup completed successfully!" Write-Host "==========================================" -ForegroundColor Green Write-Host "" Write-Info "Next steps:" Write-Host " Build the project: cmake --build --preset windows" Write-Host " Build the electron app: cmake --build --preset windows --target electron-app" Write-Host "" Write-Info "For more information, see the README.md file" lemonade-sdk-lemonade-d88f5d9/setup.sh000077500000000000000000000273361515430344400200110ustar00rootroot00000000000000#!/bin/bash # Lemonade development environment setup script # This script prepares the development environment for building Lemonade set -e # Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color # Helper functions print_info() { echo -e "${BLUE}[INFO]${NC} $1" } print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" } print_warning() { echo -e "${YELLOW}[WARNING]${NC} $1" } print_error() { echo -e "${RED}[ERROR]${NC} $1" } # Check if command exists command_exists() { command -v "$1" >/dev/null 2>&1 } # Check if running as root is_root() { [ "$(id -u)" -eq 0 ] } # Use sudo only if not root maybe_sudo() { if is_root; then "$@" else sudo "$@" fi } # Detect OS if [[ "$OSTYPE" == "linux-gnu"* ]]; then OS="linux" elif [[ "$OSTYPE" == "darwin"* ]]; then OS="macos" else print_error "Unsupported OS: $OSTYPE" exit 1 fi print_info "Lemonade Development Setup" print_info "Operating System: $OS" echo "" # Arrays to track missing dependencies missing_packages=() install_commands=() # Check pre-commit print_info "Checking pre-commit installation..." if ! command_exists pre-commit; then print_warning "pre-commit not found" if [ "$OS" = "linux" ] && command_exists pacman; then missing_packages+=("pre-commit") elif [ "$OS" = "linux" ] && command_exists apt; then missing_packages+=("pre-commit") elif [ "$OS" = "linux" ] && command_exists dnf; then missing_packages+=("pre-commit") elif [ "$OS" = "macos" ] && command_exists brew; then missing_packages+=("pre-commit") elif command_exists pip || command_exists pip3; then missing_packages+=("pre-commit (via pip)") else print_error "No package manager found to install pre-commit" exit 1 fi else print_success "pre-commit is already installed" fi # Check required build tools print_info "Checking required build tools..." required_tools=("git" "cmake" "ninja" "gcc" "g++") missing_tools=() for tool in "${required_tools[@]}"; do if command_exists "$tool"; then print_success "$tool is installed" else print_warning "$tool not found" missing_tools+=("$tool") fi done if [ ${#missing_tools[@]} -gt 0 ]; then if [ "$OS" = "linux" ]; then if command_exists apt; then missing_packages+=("git" "cmake" "ninja-build" "build-essential") elif command_exists pacman; then missing_packages+=("git" "cmake" "ninja" "base-devel") elif command_exists dnf; then missing_packages+=("git" "cmake" "ninja-build" "gcc" "gcc-c++" "make") fi elif [ "$OS" = "macos" ]; then missing_packages+=("git" "cmake" "ninja") fi fi # Check pkg-config and required libraries print_info "Checking pkg-config and required libraries..." if command_exists pkg-config; then print_success "pkg-config is installed" # Check for required libraries using pkg-config libs_to_check=("libcurl" "openssl" "zlib" "libsystemd" "libdrm") missing_libs=() for lib in "${libs_to_check[@]}"; do if pkg-config --exists "$lib" 2>/dev/null; then print_success "$lib is installed" else print_warning "$lib not found" missing_libs+=("$lib") fi done if [ ${#missing_libs[@]} -gt 0 ]; then if [ "$OS" = "linux" ]; then if command_exists apt; then # Map pkg-config names to apt packages for lib in "${missing_libs[@]}"; do case "$lib" in libcurl) missing_packages+=("libcurl4-openssl-dev") ;; openssl) missing_packages+=("libssl-dev") ;; zlib) missing_packages+=("zlib1g-dev") ;; libsystemd) missing_packages+=("libsystemd-dev") ;; libdrm) missing_packages+=("libdrm-dev") ;; esac done elif command_exists pacman; then # Map pkg-config names to pacman packages for lib in "${missing_libs[@]}"; do case "$lib" in libcurl) missing_packages+=("curl") ;; openssl) missing_packages+=("openssl") ;; zlib) missing_packages+=("zlib") ;; libsystemd) missing_packages+=("systemd") ;; libdrm) missing_packages+=("libdrm") ;; esac done elif command_exists dnf; then # Map pkg-config names to dnf packages for lib in "${missing_libs[@]}"; do case "$lib" in libcurl) missing_packages+=("libcurl-devel") ;; openssl) missing_packages+=("openssl-devel") ;; zlib) missing_packages+=("zlib-devel") ;; libsystemd) missing_packages+=("systemd-devel") ;; libdrm) missing_packages+=("libdrm-devel") ;; esac done fi elif [ "$OS" = "macos" ]; then # macOS typically has these via Xcode Command Line Tools # but can be explicitly installed via brew if needed for lib in "${missing_libs[@]}"; do case "$lib" in libcurl) missing_packages+=("curl") ;; openssl) missing_packages+=("openssl") ;; zlib) missing_packages+=("zlib") ;; esac done fi fi else print_warning "pkg-config not found, assuming libraries need to be installed" if [ "$OS" = "linux" ]; then if command_exists apt; then missing_packages+=("pkg-config" "libcurl4-openssl-dev" "libssl-dev" "zlib1g-dev" "libsystemd-dev" "libdrm-dev") elif command_exists pacman; then missing_packages+=("pkgconf" "curl" "openssl" "zlib" "systemd" "libdrm") elif command_exists dnf; then missing_packages+=("pkgconfig" "libcurl-devel" "openssl-devel" "zlib-devel" "systemd-devel" "libdrm-devel") fi elif [ "$OS" = "macos" ]; then missing_packages+=("pkg-config" "curl" "openssl" "zlib" "libdrm") fi fi # Check Node.js and npm print_info "Checking Node.js and npm installation..." if ! command_exists node; then print_warning "Node.js not found" if [ "$OS" = "linux" ]; then if command_exists apt || command_exists pacman || command_exists dnf; then missing_packages+=("nodejs" "npm") fi elif [ "$OS" = "macos" ]; then if command_exists brew; then missing_packages+=("node") else print_error "Homebrew not found. Please install Homebrew first: https://brew.sh/" exit 1 fi fi else print_success "Node.js is already installed" fi if command_exists node && ! command_exists npm; then print_warning "npm not found" missing_packages+=("npm") fi # Check for KaTeX fonts (optional but recommended for packaging) print_info "Checking KaTeX fonts installation..." if [ "$OS" = "linux" ]; then KATEX_FONTS_DIR="/usr/share/fonts/truetype/katex" if [ -d "$KATEX_FONTS_DIR" ]; then print_success "KaTeX fonts are already installed" else print_warning "KaTeX fonts not found (optional, enables symlinks in packages)" if command_exists apt; then missing_packages+=("fonts-katex") fi fi fi echo "" # If there are missing packages, prompt for installation if [ ${#missing_packages[@]} -gt 0 ]; then print_warning "Missing dependencies detected:" for pkg in "${missing_packages[@]}"; do echo " - $pkg" done echo "" # Build installation command if [ "$OS" = "linux" ]; then if command_exists apt; then if is_root; then install_cmd="apt update && apt install -y ${missing_packages[*]}" else install_cmd="sudo apt update && sudo apt install -y ${missing_packages[*]}" fi elif command_exists pacman; then if is_root; then install_cmd="pacman -Syu --needed --noconfirm ${missing_packages[*]}" else install_cmd="sudo pacman -Syu --needed --noconfirm ${missing_packages[*]}" fi elif command_exists dnf; then if is_root; then install_cmd="dnf install -y ${missing_packages[*]}" else install_cmd="sudo dnf install -y ${missing_packages[*]}" fi fi elif [ "$OS" = "macos" ]; then install_cmd="brew install ${missing_packages[*]}" fi print_info "The following command will be executed:" echo " $install_cmd" echo "" # Check if running in CI environment if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ]; then print_info "CI environment detected, proceeding with automatic installation..." else read -p "Do you want to install these dependencies? (y/N): " -n 1 -r echo "" if [[ ! $REPLY =~ ^[Yy]$ ]]; then print_error "Installation aborted by user" exit 1 fi fi print_info "Installing dependencies..." # Install based on OS and package manager if [ "$OS" = "linux" ]; then if command_exists apt; then maybe_sudo apt update # Check if pre-commit needs pip if [[ " ${missing_packages[*]} " =~ " pre-commit " ]]; then maybe_sudo apt install -y pre-commit fi # Install other packages other_packages=("${missing_packages[@]}") other_packages=("${other_packages[@]/pre-commit/}") if [ ${#other_packages[@]} -gt 0 ]; then maybe_sudo apt install -y ${other_packages[@]} fi elif command_exists pacman; then maybe_sudo pacman -Syu --needed --noconfirm ${missing_packages[@]} elif command_exists dnf; then maybe_sudo dnf install -y ${missing_packages[@]} fi elif [ "$OS" = "macos" ]; then brew install ${missing_packages[@]} fi # Install pre-commit via pip if no package manager version available if [[ " ${missing_packages[*]} " =~ " pre-commit (via pip) " ]]; then if command_exists pip; then pip install pre-commit elif command_exists pip3; then pip3 install pre-commit fi fi print_success "Dependencies installed successfully" echo "" else print_success "All dependencies are already installed" echo "" fi # Install pre-commit hooks if [ -f ".pre-commit-config.yaml" ] && [ ! -f ".git/hooks/pre-commit" ] && [ -z "$CI" ] && [ -z "$GITHUB_ACTIONS" ]; then print_info "Installing pre-commit hooks..." pre-commit install print_success "pre-commit hooks installed" fi echo "" # Clean and create build directory print_info "Preparing build directory..." if [ -d "build" ]; then print_warning "Removing existing build directory..." rm -rf build fi mkdir -p build print_success "Build directory created" echo "" # Configure with CMake presets print_info "Configuring CMake with presets..." cmake --preset default print_success "CMake configured successfully" echo "" echo "==========================================" print_success "Setup completed successfully!" echo "==========================================" echo "" print_info "Next steps:" echo " Build the project: cmake --build --preset default" echo " Build the electron app: cmake --build --preset default --target electron-app" echo " Build AppImage (Linux): cmake --build --preset default --target appimage" echo "" print_info "For more information, see the docs/dev-getting-started.md file" lemonade-sdk-lemonade-d88f5d9/src/000077500000000000000000000000001515430344400170665ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/src/app/000077500000000000000000000000001515430344400176465ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/src/app/.gitignore000066400000000000000000000006111515430344400216340ustar00rootroot00000000000000# Node modules node_modules/ # Build output dist/ dist-app/ build/ # OS files .DS_Store Thumbs.db # IDE .vscode/ .idea/ *.swp *.swo *~ # Logs logs/ *.log npm-debug.log* yarn-debug.log* yarn-error.log* # Environment variables .env .env.local # Package manager # Note: package-lock.json is now tracked for reproducible CI/CD builds yarn.lock pnpm-lock.yaml # Temporary files *.tmp .temp/ lemonade-sdk-lemonade-d88f5d9/src/app/README.md000066400000000000000000000061441515430344400211320ustar00rootroot00000000000000# Lemonade Desktop App A desktop GUI for interacting with the Lemonade Server. ## Overview This app provides a native desktop experience for managing models and chatting with LLMs running on `lemonade-router`. It connects to the server via HTTP API and offers a modern, resizable panel-based interface. **Key Features:** - Model management (list, pull, load/unload) - Chat interface with markdown/code rendering and LaTeX support - Real-time server log viewer - Persistent layout and inference settings - Custom frameless window with zoom controls ## Code Structure ``` src/app/ ├── main.js # Electron main process ├── preload.js # IPC bridge (contextIsolation) ├── webpack.config.js # Bundler config ├── package.json # Dependencies and build scripts │ ├── src/ │ └── renderer/ # React UI (TypeScript) │ ├── App.tsx # Root component, layout orchestration │ ├── TitleBar.tsx # Custom window controls │ ├── ModelManager.tsx # Model list and actions │ ├── ChatWindow.tsx # LLM chat interface │ ├── LogsWindow.tsx # Server log viewer │ ├── CenterPanel.tsx # Welcome/info panel │ ├── SettingsModal.tsx # Inference parameters │ └── utils/ # API helpers and config ``` ``` ## Architecture ``` ┌─────────────────────────────────────────────────┐ │ Electron Main Process (main.js) │ │ Window management, IPC, settings │ ├─────────────────────────────────────────────────┤ │ Preload Script (contextIsolation) │ │ Exposes safe IPC bridge to renderer │ ├─────────────────────────────────────────────────┤ │ React Renderer (TypeScript) │ │ UI components, state management │ ├─────────────────────────────────────────────────┤ │ HTTP API │ │ Communicates with lemonade-router │ └─────────────────────────────────────────────────┘ ``` ## Prerequisites - **Node.js** v18 or higher - **npm** (comes with Node.js) ## Building ```bash cd src/app # Install dependencies npm install # Development (with DevTools) npm run dev # Production build npm run build ``` ## Development Scripts ```bash npm start # Build and run npm run dev # Build and run with DevTools npm run build # Production build (creates installer) npm run build:renderer # Build renderer only (webpack) npm run watch:renderer # Watch mode for renderer ``` lemonade-sdk-lemonade-d88f5d9/src/app/assets/000077500000000000000000000000001515430344400211505ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/src/app/assets/chevron-down-gray.svg000066400000000000000000000003531515430344400252430ustar00rootroot00000000000000 lemonade-sdk-lemonade-d88f5d9/src/app/assets/chevron-down-light.svg000066400000000000000000000003531515430344400254100ustar00rootroot00000000000000 lemonade-sdk-lemonade-d88f5d9/src/app/assets/chevron-down-white.svg000066400000000000000000000003531515430344400254210ustar00rootroot00000000000000 lemonade-sdk-lemonade-d88f5d9/src/app/assets/chevron-down.svg000066400000000000000000000003561515430344400243060ustar00rootroot00000000000000 lemonade-sdk-lemonade-d88f5d9/src/app/assets/favicon.ico000077700000000000000000000000001515430344400305002../../../docs/assets/favicon.icoustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/src/app/assets/logo.svg000066400000000000000000000114341515430344400226340ustar00rootroot00000000000000 lemonade-sdk-lemonade-d88f5d9/src/app/index.html000066400000000000000000000035361515430344400216520ustar00rootroot00000000000000 Lemonade App

Lemonade

Powered by Electron & Python

Welcome

This is your Lemonade with an Electron frontend and Python backend.

Backend Status

Checking connection...

Getting Started

  • The Python backend is running on port 5000
  • Click the button above to test the connection
  • Start building your lemonade features here

Lemonade App v1.0.0

lemonade-sdk-lemonade-d88f5d9/src/app/main.js000066400000000000000000000751431515430344400211420ustar00rootroot00000000000000const { app, BrowserWindow, ipcMain, shell, session, screen, webFrameMain, clipboard } = require('electron'); const path = require('path'); const fs = require('fs'); const os = require('os'); const { spawn, spawnSync } = require('child_process'); const strict = require('assert/strict'); const dgram = require('dgram'); const DEFAULT_MIN_WIDTH = 400; const DEFAULT_MIN_HEIGHT = 600; const ABSOLUTE_MIN_WIDTH = 400; // Preferred initial window size to properly display center menu with both cards const PREFERRED_INITIAL_WIDTH = 1440; const PREFERRED_INITIAL_HEIGHT = 900; const MIN_ZOOM_LEVEL = -2; const MAX_ZOOM_LEVEL = 3; const ZOOM_STEP = 0.2; const BASE_SETTING_VALUES = { temperature: 0.7, topK: 40, topP: 0.9, repeatPenalty: 1.1, enableThinking: true, collapseThinkingByDefault: false, baseURL: '', apiKey: '', }; let mainWindow; let currentMinWidth = DEFAULT_MIN_WIDTH; const SETTINGS_FILE_NAME = 'app_settings.json'; const SETTINGS_UPDATED_CHANNEL = 'settings-updated'; const SERVER_PORT_UPDATED_CHANNEL = 'server-port-updated'; const CONNECTION_SETTINGS_UPDATED_CHANNEL = 'connection-settings-updated' let cachedServerPort = 8000; // Default port /** * Parse and normalize a server base URL. * - Adds http:// if no protocol specified * - Strips trailing slashes * - Returns null if URL is invalid */ const normalizeServerUrl = (url) => { if (!url || typeof url !== 'string') { return null; } let normalized = url.trim(); if (!normalized) { return null; } // Add http:// if no protocol specified if (!normalized.match(/^https?:\/\//i)) { normalized = 'http://' + normalized; } // Strip trailing slashes normalized = normalized.replace(/\/+$/, ''); // Validate URL format try { new URL(normalized); return normalized; } catch (e) { console.error('Invalid server URL:', url, e.message); return null; } }; const DEFAULT_APP_SETTINGS = Object.freeze({ temperature: { value: BASE_SETTING_VALUES.temperature, useDefault: true }, topK: { value: BASE_SETTING_VALUES.topK, useDefault: true }, topP: { value: BASE_SETTING_VALUES.topP, useDefault: true }, repeatPenalty: { value: BASE_SETTING_VALUES.repeatPenalty, useDefault: true }, enableThinking: { value: BASE_SETTING_VALUES.enableThinking, useDefault: true }, collapseThinkingByDefault: { value: BASE_SETTING_VALUES.collapseThinkingByDefault, useDefault: true }, baseURL: { value: BASE_SETTING_VALUES.baseURL, useDefault: true }, apiKey: { value: BASE_SETTING_VALUES.apiKey, useDefault: true }, }); const NUMERIC_APP_SETTING_LIMITS = Object.freeze({ temperature: { min: 0, max: 2 }, topK: { min: 1, max: 100 }, topP: { min: 0, max: 1 }, repeatPenalty: { min: 1, max: 2 }, }); const NUMERIC_APP_SETTING_KEYS = ['temperature', 'topK', 'topP', 'repeatPenalty']; const getHomeDirectory = () => { if (typeof os.homedir === 'function') { return os.homedir(); } return process.env.HOME || process.env.USERPROFILE || ''; }; const getLocalInterfaceAddresses = () => { const interfaces = os.networkInterfaces ? os.networkInterfaces() : {}; const localAddresses = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']); Object.values(interfaces || {}).forEach((entries) => { if (!Array.isArray(entries)) { return; } entries.forEach((entry) => { if (entry && typeof entry.address === 'string') { localAddresses.add(entry.address); } }); }); return localAddresses; }; const isBeaconFromLocalMachine = (address) => { if (!address || typeof address !== 'string') { return false; } const normalized = address.replace(/^::ffff:/, ''); const localAddresses = getLocalInterfaceAddresses(); return localAddresses.has(address) || localAddresses.has(normalized); }; const getCacheDirectory = () => { const homeDir = getHomeDirectory(); if (!homeDir) { return ''; } return path.join(homeDir, '.cache', 'lemonade'); }; const getAppSettingsFilePath = () => { const cacheDir = getCacheDirectory(); if (!cacheDir) { return ''; } return path.join(cacheDir, SETTINGS_FILE_NAME); }; // Default layout settings - which panels are visible and their sizes const DEFAULT_LAYOUT_SETTINGS = Object.freeze({ isChatVisible: true, isModelManagerVisible: true, isMarketplaceVisible: true, // Renamed from isCenterPanelVisible to reset user preference isLogsVisible: false, modelManagerWidth: 280, chatWidth: 350, logsHeight: 200, }); const LAYOUT_SIZE_LIMITS = Object.freeze({ modelManagerWidth: { min: 200, max: 500 }, chatWidth: { min: 250, max: 800 }, logsHeight: { min: 100, max: 400 }, }); const DEFAULT_TTS_SETTINGS = Object.freeze({ model: { value: 'kokoro-v1', useDefault: true }, userVoice: { value: 'fable', useDefault: true }, assistantVoice: { value: 'alloy', useDefault: true }, enableTTS: { value: false, useDefault: true }, enableUserTTS: { value: false, useDefault: true } }); const createDefaultAppSettings = () => ({ temperature: { ...DEFAULT_APP_SETTINGS.temperature }, topK: { ...DEFAULT_APP_SETTINGS.topK }, topP: { ...DEFAULT_APP_SETTINGS.topP }, repeatPenalty: { ...DEFAULT_APP_SETTINGS.repeatPenalty }, enableThinking: { ...DEFAULT_APP_SETTINGS.enableThinking }, collapseThinkingByDefault: { ...DEFAULT_APP_SETTINGS.collapseThinkingByDefault }, baseURL: { ...DEFAULT_APP_SETTINGS.baseURL }, apiKey: { ...DEFAULT_APP_SETTINGS.apiKey }, layout: { ...DEFAULT_LAYOUT_SETTINGS }, tts: {...DEFAULT_TTS_SETTINGS } }); const clampValue = (value, min, max) => { if (!Number.isFinite(value)) { return min; } return Math.min(Math.max(value, min), max); }; const sanitizeAppSettings = (incoming = {}) => { const sanitized = createDefaultAppSettings(); NUMERIC_APP_SETTING_KEYS.forEach((key) => { const rawSetting = incoming[key]; if (!rawSetting || typeof rawSetting !== 'object') { return; } const limits = NUMERIC_APP_SETTING_LIMITS[key]; const useDefault = typeof rawSetting.useDefault === 'boolean' ? rawSetting.useDefault : sanitized[key].useDefault; const numericValue = useDefault ? sanitized[key].value : typeof rawSetting.value === 'number' ? clampValue(rawSetting.value, limits.min, limits.max) : sanitized[key].value; sanitized[key] = { value: numericValue, useDefault, }; }); const rawEnableThinking = incoming.enableThinking; if (rawEnableThinking && typeof rawEnableThinking === 'object') { const useDefault = typeof rawEnableThinking.useDefault === 'boolean' ? rawEnableThinking.useDefault : sanitized.enableThinking.useDefault; sanitized.enableThinking = { value: useDefault ? sanitized.enableThinking.value : typeof rawEnableThinking.value === 'boolean' ? rawEnableThinking.value : sanitized.enableThinking.value, useDefault, }; } const rawCollapseThinkingByDefault = incoming.collapseThinkingByDefault; if (rawCollapseThinkingByDefault && typeof rawCollapseThinkingByDefault === 'object') { const useDefault = typeof rawCollapseThinkingByDefault.useDefault === 'boolean' ? rawCollapseThinkingByDefault.useDefault : sanitized.collapseThinkingByDefault.useDefault; sanitized.collapseThinkingByDefault = { value: useDefault ? sanitized.collapseThinkingByDefault.value : typeof rawCollapseThinkingByDefault.value === 'boolean' ? rawCollapseThinkingByDefault.value : sanitized.collapseThinkingByDefault.value, useDefault, }; } const rawBaseURL = incoming.baseURL; if (rawBaseURL && typeof rawBaseURL === 'object') { const useDefault = typeof rawBaseURL.useDefault === 'boolean' ? rawBaseURL.useDefault : sanitized.baseURL.useDefault; sanitized.baseURL = { value: useDefault ? sanitized.baseURL.value : typeof rawBaseURL.value === 'string' ? rawBaseURL.value : sanitized.baseURL.value, useDefault, }; } const rawApiKey = incoming.apiKey; if (rawApiKey && typeof rawApiKey === 'object') { const useDefault = typeof rawApiKey.useDefault === 'boolean' ? rawApiKey.useDefault : sanitized.apiKey.useDefault; sanitized.apiKey = { value: useDefault ? sanitized.apiKey.value : typeof rawApiKey.value === 'string' ? rawApiKey.value : sanitized.apiKey.value, useDefault, }; } // Sanitize layout settings const rawLayout = incoming.layout; if (rawLayout && typeof rawLayout === 'object') { // Sanitize boolean visibility settings ['isChatVisible', 'isModelManagerVisible', 'isMarketplaceVisible', 'isLogsVisible'].forEach((key) => { if (typeof rawLayout[key] === 'boolean') { sanitized.layout[key] = rawLayout[key]; } }); // Sanitize numeric size settings with limits Object.entries(LAYOUT_SIZE_LIMITS).forEach(([key, { min, max }]) => { const value = rawLayout[key]; if (typeof value === 'number' && Number.isFinite(value)) { sanitized.layout[key] = Math.min(Math.max(Math.round(value), min), max); } }); } // Sanitize TTS settings const rawTTS = incoming.tts; if (rawTTS && typeof rawTTS === 'object') { const ttsKeys = Object.keys(rawTTS); ttsKeys.forEach((key) => { if (rawTTS[key] && typeof rawTTS[key] === 'object') { const useDefault = (typeof rawTTS[key].useDefault === 'boolean') ? rawTTS[key].useDefault : sanitized.tts[key].useDefault; const value = useDefault ? sanitized.tts[key].value : (typeof rawTTS[key].value === 'string' || typeof rawTTS[key].value === 'boolean') ? rawTTS[key].value : sanitized.tts[key].value; sanitized.tts[key] = { value, useDefault }; } }); } return sanitized; }; const readAppSettingsFile = async () => { const settingsPath = getAppSettingsFilePath(); if (!settingsPath) { return createDefaultAppSettings(); } try { const content = await fs.promises.readFile(settingsPath, 'utf-8'); return sanitizeAppSettings(JSON.parse(content)); } catch (error) { if (error && error.code === 'ENOENT') { return createDefaultAppSettings(); } console.error('Failed to read app settings:', error); return createDefaultAppSettings(); } }; const writeAppSettingsFile = async (settings) => { const settingsPath = getAppSettingsFilePath(); if (!settingsPath) { throw new Error('Unable to locate the Lemonade cache-directory'); } const sanitized = sanitizeAppSettings(settings); await fs.promises.mkdir(path.dirname(settingsPath), { recursive: true }); await fs.promises.writeFile(settingsPath, JSON.stringify(sanitized, null, 2), 'utf-8'); return sanitized; }; /** * Get base URL from Settings file. * */ const getBaseURLFromConfig = async () => { const settings = await readAppSettingsFile(); const defaultBaseUrl = settings.baseURL.value; if (defaultBaseUrl) { const normalized = normalizeServerUrl(defaultBaseUrl); if (normalized) { return normalized; } return null; } }; const broadcastSettingsUpdated = (settings) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send(SETTINGS_UPDATED_CHANNEL, settings); } }; const broadcastConnectionSettingsUpdated = (baseURL, apiKey) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send(CONNECTION_SETTINGS_UPDATED_CHANNEL, baseURL, apiKey); } }; const broadcastServerPortUpdated = (port) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send(SERVER_PORT_UPDATED_CHANNEL, port); } }; const fetchWithApiKey = async (entpoint) => { let serverUrl = await getBaseURLFromConfig(); let apiKey = (await readAppSettingsFile()).apiKey.value; if (!serverUrl) { serverUrl = cachedServerPort ? `http://localhost:${cachedServerPort}` : 'http://localhost:8000'; } const options = {timeout: 3000}; if(apiKey != null && apiKey != '') { options.headers = { Authorization: `Bearer ${apiKey}`, } } return await fetch(`${serverUrl}${entpoint}`, options); } const ensureTrayRunning = () => { return new Promise((resolve) => { if (process.platform !== 'darwin') { resolve(); return; } const binaryPath = '/usr/local/bin/lemonade-server'; if (!fs.existsSync(binaryPath)) { console.error(`CRITICAL: Binary not found at ${binaryPath}`); resolve(); return; } console.log('--- STARTING TRAY MANUALLY ---'); // 1. NUCLEAR CLEANUP (Crucial!) // We must kill any "Ghost" processes and delete the lock files // or the new one will think it's already running and quit immediately. try { gracefulKillBlocking('lemonade-server tray'); // Delete the lock file that cause "Already Running" error const lock = '/tmp/lemonade_Tray.lock'; if (fs.existsSync(lock)) fs.unlinkSync(lock); console.log('Cleanup complete (Zombies killed, Locks deleted).'); } catch (e) { console.error('Cleanup warning:', e.message); } // 2. PREPARE ENVIRONMENT // macOS GUI apps don't have /usr/local/bin in PATH. We must add it. const env = { ...process.env }; env.PATH = `${env.PATH}:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`; // Ensure it can find libraries in /usr/local/lib env.DYLD_LIBRARY_PATH = `${env.DYLD_LIBRARY_PATH}:/usr/local/lib`; // 3. LAUNCH console.log('Spawning tray process...'); const trayProcess = spawn(binaryPath, ['tray'], { detached: true, // Allows it to run independently of the main window env: env, // Pass our fixed environment stdio: 'ignore' // Ignore output so it doesn't hang the parent }); // Unref so Electron doesn't wait for it to exit trayProcess.unref(); console.log(`Tray launched! (PID: ${trayProcess.pid})`); // Give it a moment to initialize setTimeout(resolve, 1000); }); }; function gracefulKillBlocking(processPattern) { const TIMEOUT_MS = 30000; // Send SIGTERM (Polite kill) const killResult = spawnSync('pkill', ['-f', processPattern]); // If pkill returned non-zero, the process wasn't running. We are done. if (killResult.status !== 0) { return; } // 2. Poll for exit const deadline = Date.now() + TIMEOUT_MS; let isRunning = true; while (isRunning && Date.now() < deadline) { // Check if process exists using pgrep const checkResult = spawnSync('pgrep', ['-f', processPattern]); if (checkResult.status !== 0) { // pgrep returned non-zero (process not found) -> It exited! isRunning = false; } else { // Process still exists -> Block for 1 second spawnSync('sleep', ['1']); } } // 3. Force Kill (SIGKILL) if the flag is still true if (isRunning) { spawnSync('pkill', ['-9', '-f', processPattern]); } } /** * Discovers the lemonade server using UDP broadcast beacons. * The server broadcasts its presence on UDP port 8000 with a JSON payload * containing the service name, hostname, and URL. * * This works for both local and remote servers on the same network. */ const discoverServerPort = () => { const DEFAULT_PORT = 8000; const BEACON_PORT = 8000; const DISCOVERY_TIMEOUT_MS = 5000; return new Promise((resolve) => { // Always ensure tray is running on macOS ensureTrayRunning().then(() => { const socket = dgram.createSocket({ type: 'udp4', reuseAddr: true }); let discovered = false; let timeoutId = null; const cleanup = () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } try { socket.close(); } catch (e) { // Socket may already be closed } }; socket.on('error', (err) => { console.error('UDP discovery socket error:', err); cleanup(); if (!discovered) { discovered = true; const fallbackPort = cachedServerPort || DEFAULT_PORT; console.log(`Falling back to cached port due to socket error: ${fallbackPort}`); resolve(fallbackPort); } }); socket.on('message', (msg, rinfo) => { if (discovered) return; // Ignore beacons from other machines. We only derive localhost port // in discovery mode, so remote beacons can cause invalid localhost targets. if (!isBeaconFromLocalMachine(rinfo?.address)) { return; } try { const payload = JSON.parse(msg.toString()); // Verify this is a lemonade service beacon if (payload.service === 'lemonade' && payload.url) { console.log(`Discovered lemonade server via UDP beacon from ${rinfo.address}:${rinfo.port}:`, payload); // Extract port from the URL (format: http://IP:PORT/api/v1/) const urlMatch = payload.url.match(/:(\d+)\//); if (urlMatch && urlMatch[1]) { const port = parseInt(urlMatch[1], 10); if (!isNaN(port) && port > 0 && port < 65536) { discovered = true; cleanup(); console.log('Discovered server port via network beacon:', port); resolve(port); return; } } } } catch (parseError) { // Not a valid JSON beacon, ignore } }); socket.on('listening', () => { const address = socket.address(); console.log(`UDP discovery listening on ${address.address}:${address.port}`); }); // Set timeout for discovery timeoutId = setTimeout(() => { if (!discovered) { discovered = true; cleanup(); const fallbackPort = cachedServerPort || DEFAULT_PORT; console.log(`UDP discovery timeout, falling back to cached port: ${fallbackPort}`); resolve(fallbackPort); } }, DISCOVERY_TIMEOUT_MS); // Bind to the beacon port to receive broadcasts try { socket.bind(BEACON_PORT); } catch (bindError) { console.error('Failed to bind UDP socket:', bindError); cleanup(); if (!discovered) { discovered = true; resolve(cachedServerPort || DEFAULT_PORT); } } }); }); }; /** * Background beacon listener that continuously monitors for lemonade server * UDP beacons on localhost. When a beacon is received with a different port * than the currently cached port, it updates the cached port and broadcasts * the change to all renderer windows. */ let beaconSocket = null; const startBeaconListener = async () => { const BEACON_PORT = 8000; // Don't listen if an explicit base URL is configured const baseURL = await getBaseURLFromConfig(); if (baseURL) { console.log('Beacon listener skipped - using explicit server URL:', baseURL); return; } if (beaconSocket) { return; // Already listening } const socket = dgram.createSocket({ type: 'udp4', reuseAddr: true }); beaconSocket = socket; socket.on('error', (err) => { console.error('Beacon listener socket error:', err); try { socket.close(); } catch (e) { /* ignore */ } beaconSocket = null; // Retry after 10 seconds setTimeout(() => startBeaconListener(), 10000); }); socket.on('message', (msg, rinfo) => { try { if (!isBeaconFromLocalMachine(rinfo?.address)) { return; } const payload = JSON.parse(msg.toString()); if (payload.service === 'lemonade' && payload.url) { const urlMatch = payload.url.match(/:(\d+)\//); if (urlMatch && urlMatch[1]) { const port = parseInt(urlMatch[1], 10); if (!isNaN(port) && port > 0 && port < 65536 && port !== cachedServerPort) { console.log(`Beacon listener detected server port change: ${cachedServerPort} -> ${port}`); cachedServerPort = port; broadcastServerPortUpdated(port); } } } } catch (e) { // Not a valid JSON beacon, ignore } }); socket.on('listening', () => { const address = socket.address(); console.log(`Beacon listener started on ${address.address}:${address.port}`); }); try { socket.bind(BEACON_PORT); } catch (bindError) { console.error('Failed to bind beacon listener socket:', bindError); beaconSocket = null; // Retry after 10 seconds setTimeout(() => startBeaconListener(), 10000); } }; ipcMain.handle('write-clipboard', (_event, text) => { clipboard.writeText(String(text)); }); // Returns the configured server base URL, or null if using localhost discovery ipcMain.handle('get-server-base-url', async () => { return await getBaseURLFromConfig(); }); ipcMain.handle('get-server-api-key', async () => { return (await readAppSettingsFile()).apiKey.value; }); ipcMain.handle('get-app-settings', async () => { return readAppSettingsFile(); }); ipcMain.handle('save-app-settings', async (_event, payload) => { const sanitized = await writeAppSettingsFile(payload); broadcastSettingsUpdated(sanitized); broadcastConnectionSettingsUpdated(sanitized.baseURL.value, sanitized.apiKey.value); return sanitized; }); ipcMain.handle('get-version', async () => { try { const response = await fetchWithApiKey('/api/v1/health'); if(response.ok) { const data = await response.json(); return data.version || 'Unknown'; } } catch(error) { console.error('Failed to fetch version from server:', error); return 'Unknown'; } }); ipcMain.handle('discover-server-port', async () => { let baseURL = await getBaseURLFromConfig(); if (baseURL) { console.log('Port discovery skipped - using explicit server URL:', baseURL); ensureTrayRunning(); return null; } const port = await discoverServerPort(); cachedServerPort = port; broadcastServerPortUpdated(port); return port; }); ipcMain.handle('get-server-port', async () => { return cachedServerPort; }); ipcMain.handle('get-system-stats', async () => { try { const response = await fetchWithApiKey('/api/v1/system-stats'); if (response.ok) { const data = await response.json(); return { cpu_percent: data.cpu_percent, memory_gb: data.memory_gb || 0, gpu_percent: data.gpu_percent, vram_gb: data.vram_gb, npu_percent: data.npu_percent, }; } } catch (error) { console.error('Failed to fetch system stats from server:', error); } // Return null stats if server is unavailable return { cpu_percent: null, memory_gb: 0, gpu_percent: null, vram_gb: null, npu_percent: null, }; }); ipcMain.handle('get-system-info', async () => { try { const response = await fetchWithApiKey('/api/v1/system-info'); if (response.ok) { const data = await response.json(); let maxGttGb = 0; let maxVramGb = 0; const considerAmdGpu = (gpu) => { if (gpu && typeof gpu.virtual_mem_gb === 'number' && isFinite(gpu.virtual_mem_gb)) { maxGttGb = Math.max(maxGttGb, gpu.virtual_mem_gb); } if (gpu && typeof gpu.vram_gb === 'number' && isFinite(gpu.vram_gb)) { maxVramGb = Math.max(maxVramGb, gpu.vram_gb); } }; if (data.devices?.amd_igpu) { considerAmdGpu(data.devices.amd_igpu); } if (Array.isArray(data.devices?.amd_dgpu)) { data.devices.amd_dgpu.forEach(considerAmdGpu); } // Transform server response to match the About window format const systemInfo = { system: 'Unknown', os: data['OS Version'] || 'Unknown', cpu: data['Processor'] || 'Unknown', gpus: [], gtt_gb: maxGttGb > 0 ? `${maxGttGb} GB` : undefined, vram_gb: maxVramGb > 0 ? `${maxVramGb} GB` : undefined, }; // Extract GPU information from devices if (data.devices) { if (data.devices.amd_igpu?.name) { systemInfo.gpus.push(data.devices.amd_igpu.name); } if (data.devices.nvidia_igpu?.name) { systemInfo.gpus.push(data.devices.nvidia_igpu.name); } if (Array.isArray(data.devices.amd_dgpu)) { data.devices.amd_dgpu.forEach(gpu => { if (gpu.name) systemInfo.gpus.push(gpu.name); }); } if (Array.isArray(data.devices.nvidia_dgpu)) { data.devices.nvidia_dgpu.forEach(gpu => { if (gpu.name) systemInfo.gpus.push(gpu.name); }); } } return systemInfo; } } catch (error) { console.error('Failed to fetch system info from server:', error); } // Return default if server is unavailable return { system: 'Unknown', os: 'Unknown', cpu: 'Unknown', gpus: [], gtt_gb: 'Unknown', vram_gb: 'Unknown', }; }); // Get local marketplace file URL for development fallback ipcMain.handle('get-local-marketplace-url', async () => { // In development, the marketplace.html is in the docs folder at project root // In production, it would be bundled differently const docsPath = app.isPackaged ? path.join(process.resourcesPath, 'docs', 'marketplace.html') : path.join(__dirname, '..', '..', 'docs', 'marketplace.html'); // Check if file exists if (fs.existsSync(docsPath)) { return `file://${docsPath}?embedded=true&theme=dark`; } return null; }); function updateWindowMinWidth(requestedWidth) { if (!mainWindow || typeof requestedWidth !== 'number' || !isFinite(requestedWidth)) { return; } const safeWidth = Math.max(Math.round(requestedWidth), ABSOLUTE_MIN_WIDTH); if (safeWidth === currentMinWidth) { return; } currentMinWidth = safeWidth; mainWindow.setMinimumSize(currentMinWidth, DEFAULT_MIN_HEIGHT); } const clampZoomLevel = (level) => { return Math.min(Math.max(level, MIN_ZOOM_LEVEL), MAX_ZOOM_LEVEL); }; const adjustZoomLevel = (delta) => { if (!mainWindow || mainWindow.isDestroyed()) { return; } const currentLevel = mainWindow.webContents.getZoomLevel(); const nextLevel = clampZoomLevel(currentLevel + delta); if (nextLevel !== currentLevel) { mainWindow.webContents.setZoomLevel(nextLevel); } }; function createWindow() { // Get the primary display's work area (excludes taskbar/dock) const primaryDisplay = screen.getPrimaryDisplay(); const { width: screenWidth, height: screenHeight } = primaryDisplay.workAreaSize; // Use preferred size or 90% of screen size, whichever is smaller const initialWidth = Math.min(PREFERRED_INITIAL_WIDTH, Math.floor(screenWidth * 0.9)); const initialHeight = Math.min(PREFERRED_INITIAL_HEIGHT, Math.floor(screenHeight * 0.9)); mainWindow = new BrowserWindow({ width: initialWidth, height: initialHeight, minWidth: DEFAULT_MIN_WIDTH, minHeight: DEFAULT_MIN_HEIGHT, backgroundColor: '#000000', frame: false, icon: path.join(__dirname, '..', '..', 'docs', 'assets', 'favicon.ico'), webPreferences: { nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname, 'preload.js') } }); // In development, load from dist/renderer; in production from root const htmlPath = app.isPackaged ? path.join(__dirname, 'dist', 'renderer', 'index.html') : path.join(__dirname, 'dist', 'renderer', 'index.html'); mainWindow.loadFile(htmlPath); // Open all external links in the default browser mainWindow.webContents.setWindowOpenHandler(({ url }) => { // Open in external browser instead of new Electron window shell.openExternal(url); return { action: 'deny' }; // Prevent Electron from opening a new window }); const isHttpUrl = (value) => { try { const parsed = new URL(value); return parsed.protocol === 'http:' || parsed.protocol === 'https:'; } catch { return false; } }; // Intercept all anchor clicks inside loaded iframe pages and route them through // window.open so Electron's setWindowOpenHandler opens them in the system browser. const injectIframeLinkInterceptor = (frame) => { if (!frame || !isHttpUrl(frame.url) || frame === mainWindow.webContents.mainFrame) { return; } frame.executeJavaScript(` (() => { if (window.__lemonadeExternalLinkInterceptorInstalled) return; window.__lemonadeExternalLinkInterceptorInstalled = true; document.addEventListener('click', (event) => { const element = event.target && event.target.closest ? event.target.closest('a[href]') : null; if (!element) return; const href = element.href || element.getAttribute('href') || ''; if (!/^https?:\\/\\//i.test(href)) return; event.preventDefault(); event.stopPropagation(); window.open(href, '_blank', 'noopener,noreferrer'); }, true); })(); `).catch(() => {}); }; mainWindow.webContents.on('did-frame-finish-load', (_event, isMainFrame, frameProcessId, frameRoutingId) => { if (isMainFrame) { return; } const frame = webFrameMain.fromId(frameProcessId, frameRoutingId); injectIframeLinkInterceptor(frame); }); // Open DevTools in development mode if (!app.isPackaged) { mainWindow.webContents.openDevTools(); } // Listen for maximize/unmaximize events mainWindow.on('maximize', () => { mainWindow.webContents.send('maximize-change', true); }); mainWindow.on('unmaximize', () => { mainWindow.webContents.send('maximize-change', false); }); mainWindow.on('closed', function () { mainWindow = null; }); } app.on('ready', () => { ensureTrayRunning(); startBeaconListener(); createWindow(); // Allow microphone access for streaming audio transcription. // Only 'media' is auto-approved; all other permissions use Electron's default (deny). session.defaultSession.setPermissionRequestHandler((webContents, permission, callback) => { callback(permission === 'media'); }); // Window control handlers ipcMain.on('minimize-window', () => { if (mainWindow) mainWindow.minimize(); }); ipcMain.on('maximize-window', () => { if (mainWindow) { if (mainWindow.isMaximized()) { mainWindow.unmaximize(); } else { mainWindow.maximize(); } } }); ipcMain.on('close-window', () => { if (mainWindow) mainWindow.close(); }); ipcMain.on('open-external', (event, url) => { shell.openExternal(url); }); ipcMain.on('update-min-width', (_event, width) => { updateWindowMinWidth(width); }); ipcMain.on('zoom-in', () => { adjustZoomLevel(ZOOM_STEP); }); ipcMain.on('zoom-out', () => { adjustZoomLevel(-ZOOM_STEP); }); }); app.on('window-all-closed', function () { if (process.platform !== 'darwin') { app.quit(); } }); app.on('will-quit', () => { if (beaconSocket) { try { beaconSocket.close(); } catch (e) { /* ignore */ } beaconSocket = null; } }); app.on('activate', function () { if (mainWindow === null) { createWindow(); } }); lemonade-sdk-lemonade-d88f5d9/src/app/package-lock.json000066400000000000000000012667241515430344400231040ustar00rootroot00000000000000{ "name": "lemonade", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lemonade", "version": "1.0.0", "license": "MIT", "dependencies": { "@types/markdown-it": "^14.1.2", "eventsource": "^4.1.0", "highlight.js": "^11.11.1", "katex": "^0.16.25", "markdown-it": "^14.1.0", "markdown-it-texmath": "^1.0.0", "react": "^19.2.0", "react-dom": "^19.2.0" }, "devDependencies": { "@types/katex": "^0.16.7", "@types/node": "^20.10.5", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "css-loader": "^7.1.4", "electron": "^39.2.4", "electron-builder": "^26.8.1", "glob": ">=10.5.0", "html-webpack-plugin": "^5.6.6", "node-forge": ">=1.3.2", "style-loader": "^4.0.0", "ts-loader": "^9.5.4", "typescript": "^5.3.3", "webpack": "^5.105.3", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.3" } }, "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/@discoveryjs/json-ext": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", "dev": true, "license": "MIT", "engines": { "node": ">=14.17.0" } }, "node_modules/@electron/asar": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", "dev": true, "license": "MIT", "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", "minimatch": "^3.0.4" }, "bin": { "asar": "bin/asar.js" }, "engines": { "node": ">=10.12.0" } }, "node_modules/@electron/asar/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/@electron/asar/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/@electron/asar/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/@electron/asar/node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { "node": "*" } }, "node_modules/@electron/fuses": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.1", "fs-extra": "^9.0.1", "minimist": "^1.2.5" }, "bin": { "electron-fuses": "dist/bin.js" } }, "node_modules/@electron/fuses/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { "node": ">=10" } }, "node_modules/@electron/fuses/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/fuses/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/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.5.0", "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", "promise-retry": "^2.0.1" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@electron/notarize/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { "node": ">=10" } }, "node_modules/@electron/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.3.3", "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", "fs-extra": "^10.0.0", "isbinaryfile": "^4.0.8", "minimist": "^1.2.6", "plist": "^3.0.5" }, "bin": { "electron-osx-flat": "bin/electron-osx-flat.js", "electron-osx-sign": "bin/electron-osx-sign.js" }, "engines": { "node": ">=12.0.0" } }, "node_modules/@electron/osx-sign/node_modules/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/rebuild": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.0.3.tgz", "integrity": "sha512-u9vpTHRMkOYCs/1FLiSVAFZ7FbjsXK+bQuzviJZa+lG7BHZl1nz52/IcGvwa3sk80/fc3llutBkbCq10Vh8WQA==", "dev": true, "license": "MIT", "dependencies": { "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.1.1", "detect-libc": "^2.0.1", "got": "^11.7.0", "graceful-fs": "^4.2.11", "node-abi": "^4.2.0", "node-api-version": "^0.2.1", "node-gyp": "^11.2.0", "ora": "^5.1.0", "read-binary-file-arch": "^1.0.6", "semver": "^7.3.5", "tar": "^7.5.6", "yargs": "^17.0.1" }, "bin": { "electron-rebuild": "lib/cli.js" }, "engines": { "node": ">=22.12.0" } }, "node_modules/@electron/rebuild/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/@electron/universal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", "dev": true, "license": "MIT", "dependencies": { "@electron/asar": "^3.3.1", "@malept/cross-spawn-promise": "^2.0.0", "debug": "^4.3.1", "dir-compare": "^4.2.0", "fs-extra": "^11.1.1", "minimatch": "^9.0.3", "plist": "^3.1.0" }, "engines": { "node": ">=16.4" } }, "node_modules/@electron/universal/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/@electron/universal/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/@electron/universal/node_modules/fs-extra": { "version": "11.3.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { "node": ">=14.14" } }, "node_modules/@electron/universal/node_modules/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": "9.0.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@electron/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/@electron/windows-sign": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, "peer": true, "dependencies": { "cross-dirname": "^0.1.0", "debug": "^4.3.4", "fs-extra": "^11.1.1", "minimist": "^1.2.8", "postject": "^1.0.0-alpha.6" }, "bin": { "electron-windows-sign": "bin/electron-windows-sign.js" }, "engines": { "node": ">=14.14" } }, "node_modules/@electron/windows-sign/node_modules/fs-extra": { "version": "11.3.3", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { "node": ">=14.14" } }, "node_modules/@electron/windows-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", "optional": true, "peer": true, "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "node_modules/@electron/windows-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", "optional": true, "peer": true, "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.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, "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/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.4" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { "version": "0.3.11", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "node_modules/@jsonjoy.com/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/buffers": { "version": "17.67.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-17.67.0.tgz", "integrity": "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/codegen": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-core": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-core/-/fs-core-4.56.10.tgz", "integrity": "sha512-PyAEA/3cnHhsGcdY+AmIU+ZPqTuZkDhCXQ2wkXypdLitSpd6d5Ivxhnq4wa2ETRWFVJGabYynBWxIijOswSmOw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.56.10", "@jsonjoy.com/fs-node-utils": "4.56.10", "thingies": "^2.5.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-fsa": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-fsa/-/fs-fsa-4.56.10.tgz", "integrity": "sha512-/FVK63ysNzTPOnCCcPoPHt77TOmachdMS422txM4KhxddLdbW1fIbFMYH0AM0ow/YchCyS5gqEjKLNyv71j/5Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/fs-core": "4.56.10", "@jsonjoy.com/fs-node-builtins": "4.56.10", "@jsonjoy.com/fs-node-utils": "4.56.10", "thingies": "^2.5.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-node": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node/-/fs-node-4.56.10.tgz", "integrity": "sha512-7R4Gv3tkUdW3dXfXiOkqxkElxKNVdd8BDOWC0/dbERd0pXpPY+s2s1Mino+aTvkGrFPiY+mmVxA7zhskm4Ue4Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/fs-core": "4.56.10", "@jsonjoy.com/fs-node-builtins": "4.56.10", "@jsonjoy.com/fs-node-utils": "4.56.10", "@jsonjoy.com/fs-print": "4.56.10", "@jsonjoy.com/fs-snapshot": "4.56.10", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-node-builtins": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-builtins/-/fs-node-builtins-4.56.10.tgz", "integrity": "sha512-uUnKz8R0YJyKq5jXpZtkGV9U0pJDt8hmYcLRrPjROheIfjMXsz82kXMgAA/qNg0wrZ1Kv+hrg7azqEZx6XZCVw==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-node-to-fsa": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-to-fsa/-/fs-node-to-fsa-4.56.10.tgz", "integrity": "sha512-oH+O6Y4lhn9NyG6aEoFwIBNKZeYy66toP5LJcDOMBgL99BKQMUf/zWJspdRhMdn/3hbzQsZ8EHHsuekbFLGUWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/fs-fsa": "4.56.10", "@jsonjoy.com/fs-node-builtins": "4.56.10", "@jsonjoy.com/fs-node-utils": "4.56.10" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-node-utils": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-node-utils/-/fs-node-utils-4.56.10.tgz", "integrity": "sha512-8EuPBgVI2aDPwFdaNQeNpHsyqPi3rr+85tMNG/lHvQLiVjzoZsvxA//Xd8aB567LUhy4QS03ptT+unkD/DIsNg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.56.10" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-print": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-print/-/fs-print-4.56.10.tgz", "integrity": "sha512-JW4fp5mAYepzFsSGrQ48ep8FXxpg4niFWHdF78wDrFGof7F3tKDJln72QFDEn/27M1yHd4v7sKHHVPh78aWcEw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/fs-node-utils": "4.56.10", "tree-dump": "^1.1.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-snapshot": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/@jsonjoy.com/fs-snapshot/-/fs-snapshot-4.56.10.tgz", "integrity": "sha512-DkR6l5fj7+qj0+fVKm/OOXMGfDFCGXLfyHkORH3DF8hxkpDgIHbhf/DwncBMs2igu/ST7OEkexn1gIqoU6Y+9g==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", "@jsonjoy.com/fs-node-utils": "4.56.10", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/base64": { "version": "17.67.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-17.67.0.tgz", "integrity": "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/codegen": { "version": "17.67.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-17.67.0.tgz", "integrity": "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pack": { "version": "17.67.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-17.67.0.tgz", "integrity": "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "17.67.0", "@jsonjoy.com/buffers": "17.67.0", "@jsonjoy.com/codegen": "17.67.0", "@jsonjoy.com/json-pointer": "17.67.0", "@jsonjoy.com/util": "17.67.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/json-pointer": { "version": "17.67.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-17.67.0.tgz", "integrity": "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/util": "17.67.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/fs-snapshot/node_modules/@jsonjoy.com/util": { "version": "17.67.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-17.67.0.tgz", "integrity": "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "17.67.0", "@jsonjoy.com/codegen": "17.67.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/json-pack": { "version": "1.21.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/base64": "^1.1.2", "@jsonjoy.com/buffers": "^1.2.0", "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/json-pointer": "^1.0.2", "@jsonjoy.com/util": "^1.9.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/json-pack/node_modules/@jsonjoy.com/buffers": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/json-pointer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/util": "^1.9.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/util": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/buffers": "^1.0.0", "@jsonjoy.com/codegen": "^1.0.0" }, "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@jsonjoy.com/util/node_modules/@jsonjoy.com/buffers": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true, "license": "MIT" }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", "dev": true, "funding": [ { "type": "individual", "url": "https://github.com/sponsors/malept" }, { "type": "tidelift", "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" } ], "license": "Apache-2.0", "dependencies": { "cross-spawn": "^7.0.1" }, "engines": { "node": ">= 12.13.0" } }, "node_modules/@malept/flatpak-bundler": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", "lodash": "^4.17.15", "tmp-promise": "^3.0.2" }, "engines": { "node": ">= 10.0.0" } }, "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { "node": ">=10" } }, "node_modules/@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/@noble/hashes": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, "license": "MIT", "engines": { "node": ">= 16" }, "funding": { "url": "https://paulmillr.com/funding/" } }, "node_modules/@npmcli/agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-3.0.0.tgz", "integrity": "sha512-S79NdEgDQd/NGCay6TCoVzXSj74skRZIKJcpJjC5lOq34SZzyI6MqtiiWoiVWoVrTcGjNeC4ipbh1VIHlpfF5Q==", "dev": true, "license": "ISC", "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", "socks-proxy-agent": "^8.0.3" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/agent/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/@npmcli/fs": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-4.0.0.tgz", "integrity": "sha512-/xGlezI6xfGO9NwuJlnwz/K14qD1kCSAGtacBHnGzeAIuJGazcp45KP5NuyARXoKb7cwulAGWVsbeSxdG/cb0Q==", "dev": true, "license": "ISC", "dependencies": { "semver": "^7.3.5" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@npmcli/fs/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/@peculiar/asn1-cms": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.1.tgz", "integrity": "sha512-vdG4fBF6Lkirkcl53q6eOdn3XYKt+kJTG59edgRZORlg/3atWWEReRCx5rYE1ZzTTX6vLK5zDMjHh7vbrcXGtw==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/asn1-x509-attr": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-csr": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.1.tgz", "integrity": "sha512-WRWnKfIocHyzFYQTka8O/tXCiBquAPSrRjXbOkHbO4qdmS6loffCEGs+rby6WxxGdJCuunnhS2duHURhjyio6w==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-ecc": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.1.tgz", "integrity": "sha512-+Vqw8WFxrtDIN5ehUdvlN2m73exS2JVG0UAyfVB31gIfor3zWEAQPD+K9ydCxaj3MLen9k0JhKpu9LqviuCE1g==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pfx": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.1.tgz", "integrity": "sha512-nB5jVQy3MAAWvq0KY0R2JUZG8bO/bTLpnwyOzXyEh/e54ynGTatAR+csOnXkkVD9AFZ2uL8Z7EV918+qB1qDvw==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-cms": "^2.6.1", "@peculiar/asn1-pkcs8": "^2.6.1", "@peculiar/asn1-rsa": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs8": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.1.tgz", "integrity": "sha512-JB5iQ9Izn5yGMw3ZG4Nw3Xn/hb/G38GYF3lf7WmJb8JZUydhVGEjK/ZlFSWhnlB7K/4oqEs8HnfFIKklhR58Tw==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-pkcs9": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.1.tgz", "integrity": "sha512-5EV8nZoMSxeWmcxWmmcolg22ojZRgJg+Y9MX2fnE2bGRo5KQLqV5IL9kdSQDZxlHz95tHvIq9F//bvL1OeNILw==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-cms": "^2.6.1", "@peculiar/asn1-pfx": "^2.6.1", "@peculiar/asn1-pkcs8": "^2.6.1", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "@peculiar/asn1-x509-attr": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-rsa": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.1.tgz", "integrity": "sha512-1nVMEh46SElUt5CB3RUTV4EG/z7iYc7EoaDY5ECwganibQPkZ/Y2eMsTKB/LeyrUJ+W/tKoD9WUqIy8vB+CEdA==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-schema": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", "dev": true, "license": "MIT", "dependencies": { "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.1.tgz", "integrity": "sha512-O9jT5F1A2+t3r7C4VT7LYGXqkGLK7Kj1xFpz7U0isPrubwU5PbDoyYtx6MiGst29yq7pXN5vZbQFKRCP+lLZlA==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "asn1js": "^3.0.6", "pvtsutils": "^1.3.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/asn1-x509-attr": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.1.tgz", "integrity": "sha512-tlW6cxoHwgcQghnJwv3YS+9OO1737zgPogZ+CgWRUK4roEwIPzRH4JEiG770xe5HX2ATfCpmX60gurfWIF9dcQ==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.1", "asn1js": "^3.0.6", "tslib": "^2.8.1" } }, "node_modules/@peculiar/x509": { "version": "1.14.3", "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.3.tgz", "integrity": "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" }, "engines": { "node": ">=20.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/@types/body-parser": { "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "node_modules/@types/bonjour": { "version": "3.5.13", "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "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/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/connect-history-api-fallback": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", "dev": true, "license": "MIT", "dependencies": { "@types/express-serve-static-core": "*", "@types/node": "*" } }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dev": true, "license": "MIT", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/eslint": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" } }, "node_modules/@types/eslint-scope": { "version": "3.7.7", "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint": "*", "@types/estree": "*" } }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "dev": true, "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.25", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { "version": "4.19.8", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz", "integrity": "sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "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/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", "dev": true, "license": "MIT" }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true, "license": "MIT" }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "license": "MIT" }, "node_modules/@types/http-proxy": { "version": "1.17.17", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, "node_modules/@types/katex": { "version": "0.16.7", "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", "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/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "license": "MIT" }, "node_modules/@types/markdown-it": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "license": "MIT", "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "license": "MIT" }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true, "license": "MIT" }, "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": "20.19.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.21.tgz", "integrity": "sha512-CsGG2P3I5y48RPMfprQGfy4JPRZ6csfC3ltBZSRItG3ngggmNY/qs2uZKp4p9VbrpqNNSMzUZNFZKzgOGnd/VA==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, "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/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { "version": "19.2.2", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.2.tgz", "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" } }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/retry": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "dev": true, "license": "MIT" }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/serve-index": { "version": "1.9.4", "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", "dev": true, "license": "MIT", "dependencies": { "@types/express": "*" } }, "node_modules/@types/serve-static": { "version": "1.15.10", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "node_modules/@types/serve-static/node_modules/@types/send": { "version": "0.17.6", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "node_modules/@types/sockjs": { "version": "0.3.36", "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", "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/ws": { "version": "8.18.1", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" } }, "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/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "node_modules/@webassemblyjs/ieee754": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", "dev": true, "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-gen": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wasm-opt": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "node_modules/@webassemblyjs/wasm-parser": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "node_modules/@webassemblyjs/wast-printer": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "node_modules/@webpack-cli/configtest": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", "dev": true, "license": "MIT", "engines": { "node": ">=18.12.0" }, "peerDependencies": { "webpack": "^5.82.0", "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/info": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18.12.0" }, "peerDependencies": { "webpack": "^5.82.0", "webpack-cli": "6.x.x" } }, "node_modules/@webpack-cli/serve": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", "dev": true, "license": "MIT", "engines": { "node": ">=18.12.0" }, "peerDependencies": { "webpack": "^5.82.0", "webpack-cli": "6.x.x" }, "peerDependenciesMeta": { "webpack-dev-server": { "optional": true } } }, "node_modules/@xmldom/xmldom": { "version": "0.8.11", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" } }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true, "license": "Apache-2.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/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", "dev": true, "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dev": true, "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" }, "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" }, "engines": { "node": ">=0.4.0" } }, "node_modules/acorn-import-phases": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" }, "peerDependencies": { "acorn": "^8.14.0" } }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/ajv": { "version": "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==", "dev": true, "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==", "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/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==", "dev": true, "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-html-community": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", "dev": true, "engines": [ "node >= 0.8.0" ], "license": "Apache-2.0", "bin": { "ansi-html": "bin/ansi-html" } }, "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/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" }, "engines": { "node": ">= 8" } }, "node_modules/app-builder-bin": { "version": "5.0.0-alpha.12", "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz", "integrity": "sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==", "dev": true, "license": "MIT" }, "node_modules/app-builder-lib": { "version": "26.8.1", "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.8.1.tgz", "integrity": "sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==", "dev": true, "license": "MIT", "dependencies": { "@develar/schema-utils": "~2.6.5", "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.3", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.8.1", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.0.3", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "which": "^5.0.0" }, "engines": { "node": ">=14.0.0" }, "peerDependencies": { "dmg-builder": "26.8.1", "electron-builder-squirrel-windows": "26.8.1" } }, "node_modules/app-builder-lib/node_modules/@electron/get": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.1", "env-paths": "^2.2.0", "fs-extra": "^8.1.0", "got": "^11.8.5", "progress": "^2.0.3", "semver": "^6.2.0", "sumchecker": "^3.0.1" }, "engines": { "node": ">=14" }, "optionalDependencies": { "global-agent": "^3.0.0" } }, "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" }, "engines": { "node": ">=6 <7 || >=8" } }, "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/app-builder-lib/node_modules/ci-info": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/sibiraj-s" } ], "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/app-builder-lib/node_modules/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/fs-extra/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/fs-extra/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/app-builder-lib/node_modules/isexe": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" } }, "node_modules/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/which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true, "license": "MIT" }, "node_modules/asn1js": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.7.tgz", "integrity": "sha512-uLvq6KJu04qoQM6gvBfKFjlh6Gl0vOKQuR5cJMDHQkmwfMOQeN3F3SHCv9SNYSL+CRoHvOGFfllDlVz03GQjvQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" }, "engines": { "node": ">=12.0.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/balanced-match": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", "dev": true, "license": "MIT", "engines": { "node": "20 || >=22" } }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/feross" }, { "type": "patreon", "url": "https://www.patreon.com/feross" }, { "type": "consulting", "url": "https://feross.org/support" } ], "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" }, "engines": { "node": ">=6.0.0" } }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true, "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "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", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "node_modules/body-parser": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", "dev": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "~1.2.0", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", "qs": "~6.14.0", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/body-parser/node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, "node_modules/body-parser/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" } }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, "node_modules/body-parser/node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/bonjour-service": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "multicast-dns": "^7.2.5" } }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true, "license": "ISC" }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "license": "MIT", "optional": true }, "node_modules/brace-expansion": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { "node": "20 || >=22" } }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, "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-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, "license": "MIT" }, "node_modules/builder-util": { "version": "26.8.1", "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.8.1.tgz", "integrity": "sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==", "dev": true, "license": "MIT", "dependencies": { "@types/debug": "^4.1.6", "7zip-bin": "~5.2.0", "app-builder-bin": "5.0.0-alpha.12", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "node_modules/builder-util-runtime": { "version": "9.5.1", "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", "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/bundle-name": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "dev": true, "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" }, "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/bytestreamjs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=6.0.0" } }, "node_modules/cacache": { "version": "19.0.1", "resolved": "https://registry.npmjs.org/cacache/-/cacache-19.0.1.tgz", "integrity": "sha512-hdsUxulXCi5STId78vRVYEtDAjq99ICAUktLTeTYsLoTE6Z8dS0c8pWNCxwdrk9YfJeobDZc2Y186hD/5ZQgFQ==", "dev": true, "license": "ISC", "dependencies": { "@npmcli/fs": "^4.0.0", "fs-minipass": "^3.0.0", "glob": "^10.2.2", "lru-cache": "^10.0.1", "minipass": "^7.0.3", "minipass-collect": "^2.0.1", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "p-map": "^7.0.2", "ssri": "^12.0.0", "tar": "^7.4.3", "unique-filename": "^4.0.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/cacache/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/cacache/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/cacache/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/cacache/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/cacache/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/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/call-bound": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/camel-case": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", "dev": true, "license": "MIT", "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "node_modules/caniuse-lite": { "version": "1.0.30001775", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001775.tgz", "integrity": "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/caniuse-lite" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "CC-BY-4.0" }, "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/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "engines": { "node": ">= 8.10.0" }, "funding": { "url": "https://paulmillr.com/funding/" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" } }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "license": "MIT", "engines": { "node": ">=6.0" } }, "node_modules/chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", "dev": true, "license": "MIT" }, "node_modules/ci-info": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/sibiraj-s" } ], "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/clean-css": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", "dev": true, "license": "MIT", "dependencies": { "source-map": "~0.6.0" }, "engines": { "node": ">= 10.0" } }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, "engines": { "node": ">=8" } }, "node_modules/cli-spinners": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "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": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/clone-deep": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "license": "MIT", "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", "shallow-clone": "^3.0.0" }, "engines": { "node": ">=6" } }, "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/colorette": { "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "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/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "dev": true, "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" }, "engines": { "node": ">= 0.6" } }, "node_modules/compression": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "dev": true, "license": "MIT", "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/compression/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/compression/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, "node_modules/compression/node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "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/connect-history-api-fallback": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, "engines": { "node": ">= 0.6" } }, "node_modules/content-type": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "dev": true, "license": "MIT" }, "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/cross-dirname": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", "optional": true, "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" }, "engines": { "node": ">= 8" } }, "node_modules/css-loader": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.4.tgz", "integrity": "sha512-vv3J9tlOl04WjiMvHQI/9tmIrCxVrj6PFbHemBB1iihpeRbi/I4h033eoFIhwxBBqLhI0KYFS7yvynBFhIZfTw==", "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", "postcss": "^8.4.40", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", "semver": "^7.6.3" }, "engines": { "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "webpack": "^5.27.0" }, "peerDependenciesMeta": { "@rspack/core": { "optional": true }, "webpack": { "optional": true } } }, "node_modules/css-loader/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/css-select": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.0.1", "domhandler": "^4.3.1", "domutils": "^2.8.0", "nth-check": "^2.0.1" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, "node_modules/css-what": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">= 6" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, "engines": { "node": ">=4" } }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true, "license": "MIT" }, "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/default-browser": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", "dev": true, "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" }, "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/default-browser-id": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "dev": true, "license": "MIT", "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "license": "MIT", "dependencies": { "clone": "^1.0.2" }, "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-lazy-prop": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "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/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" } }, "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" }, "node_modules/dir-compare": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", "dev": true, "license": "MIT", "dependencies": { "minimatch": "^3.0.5", "p-limit": "^3.1.0 " } }, "node_modules/dir-compare/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/dir-compare/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "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/dir-compare/node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/dmg-builder": { "version": "26.8.1", "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.8.1.tgz", "integrity": "sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==", "dev": true, "license": "MIT", "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "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/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", "dev": true, "license": "MIT", "dependencies": { "@leichtgewicht/ip-codec": "^2.0.1" }, "engines": { "node": ">=6" } }, "node_modules/dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", "dev": true, "license": "MIT", "dependencies": { "utila": "~0.4" } }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", "dev": true, "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" }, "funding": { "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, "node_modules/domelementtype": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/fb55" } ], "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.2.0" }, "engines": { "node": ">= 4" }, "funding": { "url": "https://github.com/fb55/domhandler?sponsor=1" } }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" }, "funding": { "url": "https://github.com/fb55/domutils?sponsor=1" } }, "node_modules/dot-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" }, "funding": { "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { "version": "11.0.7", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "dotenv": "^16.4.5" }, "engines": { "node": ">=12" }, "funding": { "url": "https://dotenvx.com" } }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, "engines": { "node": ">= 0.4" } }, "node_modules/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/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "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": "39.2.4", "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.4.tgz", "integrity": "sha512-KxPtwpFceQKSxRtUY39piHLYhJMMyHfOhc70e6zRnKGrbRdK6hzEqssth8IGjlKOdkeT4KCvIEngnNraYk39+g==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { "@electron/get": "^2.0.0", "@types/node": "^22.7.7", "extract-zip": "^2.0.1" }, "bin": { "electron": "cli.js" }, "engines": { "node": ">= 12.20.55" } }, "node_modules/electron-builder": { "version": "26.8.1", "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.8.1.tgz", "integrity": "sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==", "dev": true, "license": "MIT", "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.8.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "cli.js", "install-app-deps": "install-app-deps.js" }, "engines": { "node": ">=14.0.0" } }, "node_modules/electron-builder-squirrel-windows": { "version": "26.8.1", "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.8.1.tgz", "integrity": "sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "app-builder-lib": "26.8.1", "builder-util": "26.8.1", "electron-winstaller": "5.4.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": "26.8.1", "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.8.1.tgz", "integrity": "sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==", "dev": true, "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.11", "builder-util": "26.8.1", "builder-util-runtime": "9.5.1", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "node_modules/electron-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-to-chromium": { "version": "1.5.302", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz", "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true, "license": "ISC" }, "node_modules/electron-winstaller": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", "dev": true, "hasInstallScript": true, "license": "MIT", "peer": true, "dependencies": { "@electron/asar": "^3.2.1", "debug": "^4.1.1", "fs-extra": "^7.0.1", "lodash": "^4.17.21", "temp": "^0.9.0" }, "engines": { "node": ">=8.0.0" }, "optionalDependencies": { "@electron/windows-sign": "^1.1.2" } }, "node_modules/electron-winstaller/node_modules/fs-extra": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", "universalify": "^0.1.0" }, "engines": { "node": ">=6 <7 || >=8" } }, "node_modules/electron/node_modules/@types/node": { "version": "22.19.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", "integrity": "sha512-LCCV0HdSZZZb34qifBsyWlUmok6W7ouER+oQIGBScS8EsZsQbrtFTUrDX4hOl+CS6p7cnNC4td+qrSVGSCTUfQ==", "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "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/enhanced-resolve": { "version": "5.20.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" }, "engines": { "node": ">=10.13.0" } }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", "dev": true, "license": "BSD-2-Clause", "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/envinfo": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", "dev": true, "license": "MIT", "bin": { "envinfo": "dist/cli.js" }, "engines": { "node": ">=4" } }, "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-module-lexer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, "license": "MIT" }, "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-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, "license": "MIT" }, "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/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" }, "engines": { "node": ">=8.0.0" } }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, "engines": { "node": ">=4.0" } }, "node_modules/esrecurse/node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/estraverse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "dev": true, "license": "MIT" }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8.x" } }, "node_modules/eventsource": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.1.0.tgz", "integrity": "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==", "license": "MIT", "dependencies": { "eventsource-parser": "^3.0.1" }, "engines": { "node": ">=20.0.0" } }, "node_modules/eventsource-parser": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "license": "MIT", "engines": { "node": ">=18.0.0" } }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "dev": true, "license": "Apache-2.0" }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "~1.20.3", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "~1.3.1", "fresh": "~0.5.2", "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "~2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, "engines": { "node": ">= 0.10.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, "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==", "dev": true, "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==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/fastify" }, { "type": "opencollective", "url": "https://opencollective.com/fastify" } ], "license": "BSD-3-Clause" }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", "dev": true, "license": "MIT", "engines": { "node": ">= 4.9.1" } }, "node_modules/faye-websocket": { "version": "0.11.4", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", "dev": true, "license": "Apache-2.0", "dependencies": { "websocket-driver": ">=0.5.1" }, "engines": { "node": ">=0.8.0" } }, "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.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", "dev": true, "license": "Apache-2.0", "dependencies": { "minimatch": "^5.0.1" } }, "node_modules/filelist/node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "license": "MIT" }, "node_modules/filelist/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } }, "node_modules/filelist/node_modules/minimatch": { "version": "5.1.9", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { "node": ">=10" } }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, "engines": { "node": ">=8" } }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/flat": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "dev": true, "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], "license": "MIT", "engines": { "node": ">=4.0" }, "peerDependenciesMeta": { "debug": { "optional": true } } }, "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/foreground-child/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/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/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "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": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "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/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "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": "13.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", "integrity": "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" }, "engines": { "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, "engines": { "node": ">= 6" } }, "node_modules/glob-to-regex.js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/lru-cache": { "version": "11.2.4", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" } }, "node_modules/glob/node_modules/path-scurry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" }, "engines": { "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "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.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "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/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "dev": true, "license": "MIT" }, "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.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, "engines": { "node": ">= 0.4" } }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, "license": "MIT", "bin": { "he": "bin/he" } }, "node_modules/highlight.js": { "version": "11.11.1", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", "license": "BSD-3-Clause", "engines": { "node": ">=12.0.0" } }, "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/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", "dev": true, "license": "MIT", "dependencies": { "inherits": "^2.0.1", "obuf": "^1.0.0", "readable-stream": "^2.0.1", "wbuf": "^1.1.0" } }, "node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "node_modules/hpack.js/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "license": "MIT" }, "node_modules/hpack.js/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", "dev": true, "license": "MIT", "dependencies": { "camel-case": "^4.1.2", "clean-css": "^5.2.2", "commander": "^8.3.0", "he": "^1.2.0", "param-case": "^3.0.4", "relateurl": "^0.2.7", "terser": "^5.10.0" }, "bin": { "html-minifier-terser": "cli.js" }, "engines": { "node": ">=12" } }, "node_modules/html-minifier-terser/node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, "license": "MIT", "engines": { "node": ">= 12" } }, "node_modules/html-webpack-plugin": { "version": "5.6.6", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.6.tgz", "integrity": "sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==", "dev": true, "license": "MIT", "dependencies": { "@types/html-minifier-terser": "^6.0.0", "html-minifier-terser": "^6.0.2", "lodash": "^4.17.21", "pretty-error": "^4.0.0", "tapable": "^2.0.0" }, "engines": { "node": ">=10.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/html-webpack-plugin" }, "peerDependencies": { "@rspack/core": "0.x || 1.x", "webpack": "^5.20.0" }, "peerDependenciesMeta": { "@rspack/core": { "optional": true }, "webpack": { "optional": true } } }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", "dev": true, "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { "type": "github", "url": "https://github.com/sponsors/fb55" } ], "license": "MIT", "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.0.0", "domutils": "^2.5.2", "entities": "^2.0.0" } }, "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-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", "dev": true, "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dev": true, "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.8" } }, "node_modules/http-parser-js": { "version": "0.5.10", "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", "dev": true, "license": "MIT" }, "node_modules/http-proxy": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", "dev": true, "license": "MIT", "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", "requires-port": "^1.0.0" }, "engines": { "node": ">=8.0.0" } }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" }, "engines": { "node": ">= 14" } }, "node_modules/http-proxy-middleware": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", "dev": true, "license": "MIT", "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", "micromatch": "^4.0.2" }, "engines": { "node": ">=12.0.0" }, "peerDependencies": { "@types/express": "^4.17.13" }, "peerDependenciesMeta": { "@types/express": { "optional": true } } }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", "dev": true, "license": "MIT", "dependencies": { "quick-lru": "^5.1.1", "resolve-alpn": "^1.0.0" }, "engines": { "node": ">=10.19.0" } }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "4" }, "engines": { "node": ">= 14" } }, "node_modules/hyperdyperid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", "dev": true, "license": "MIT", "engines": { "node": ">=10.18" } }, "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/icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", "dev": true, "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, "peerDependencies": { "postcss": "^8.1.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/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" }, "engines": { "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" } }, "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/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" } }, "node_modules/ip-address": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { "node": ">= 12" } }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, "engines": { "node": ">=8" } }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-docker": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "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-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, "engines": { "node": ">=0.10.0" } }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "dev": true, "license": "MIT", "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" }, "engines": { "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-network-error": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.1.tgz", "integrity": "sha512-6QCxa49rQbmUWLfk0nuGqzql9U8uaV2H6279bRErPBHe/109hCzsLUBUHfbEtvLIHBd6hyXbgedBSHevm43Edw==", "dev": true, "license": "MIT", "engines": { "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, "engines": { "node": ">=0.10.0" } }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-wsl": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", "dev": true, "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" }, "engines": { "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, "license": "MIT" }, "node_modules/isbinaryfile": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 18.0.0" }, "funding": { "url": "https://github.com/sponsors/gjtorikian/" } }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "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/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, "engines": { "node": ">= 10.13.0" } }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" } }, "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-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "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-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/katex": { "version": "0.16.25", "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" ], "license": "MIT", "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "node_modules/katex/node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "license": "MIT", "engines": { "node": ">= 12" } }, "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/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/launch-editor": { "version": "2.13.1", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.13.1.tgz", "integrity": "sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==", "dev": true, "license": "MIT", "dependencies": { "picocolors": "^1.1.1", "shell-quote": "^1.8.3" } }, "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/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" } }, "node_modules/loader-runner": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, "engines": { "node": ">=8" } }, "node_modules/lodash": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "dev": true, "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.3" } }, "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/make-fetch-happen": { "version": "14.0.3", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", "integrity": "sha512-QMjGbFTP0blj97EeidG5hk/QhKQ3T4ICckQGLgz38QF7Vgbk6e6FTARN8KhKxyBbWn8R0HU+bnw8aSoFPD4qtQ==", "dev": true, "license": "ISC", "dependencies": { "@npmcli/agent": "^3.0.0", "cacache": "^19.0.1", "http-cache-semantics": "^4.1.1", "minipass": "^7.0.2", "minipass-fetch": "^4.0.0", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^5.0.0", "promise-retry": "^2.0.1", "ssri": "^12.0.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/make-fetch-happen/node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/markdown-it": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "node_modules/markdown-it-texmath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/markdown-it-texmath/-/markdown-it-texmath-1.0.0.tgz", "integrity": "sha512-4hhkiX8/gus+6e53PLCUmUrsa6ZWGgJW2XCW6O0ASvZUiezIK900ZicinTDtG3kAO2kon7oUA/ReWmpW2FByxg==", "license": "MIT" }, "node_modules/markdown-it/node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, "funding": { "url": "https://github.com/fb55/entities?sponsor=1" } }, "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/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/memfs": { "version": "4.56.10", "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.56.10.tgz", "integrity": "sha512-eLvzyrwqLHnLYalJP7YZ3wBe79MXktMdfQbvMrVD80K+NhrIukCVBvgP30zTJYEEDh9hZ/ep9z0KOdD7FSHo7w==", "dev": true, "license": "Apache-2.0", "dependencies": { "@jsonjoy.com/fs-core": "4.56.10", "@jsonjoy.com/fs-fsa": "4.56.10", "@jsonjoy.com/fs-node": "4.56.10", "@jsonjoy.com/fs-node-builtins": "4.56.10", "@jsonjoy.com/fs-node-to-fsa": "4.56.10", "@jsonjoy.com/fs-node-utils": "4.56.10", "@jsonjoy.com/fs-print": "4.56.10", "@jsonjoy.com/fs-snapshot": "4.56.10", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", "thingies": "^2.5.0", "tree-dump": "^1.0.3", "tslib": "^2.0.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true, "license": "MIT" }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { "node": ">=8.6" } }, "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": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "dev": true, "license": "ISC" }, "node_modules/minimatch": { "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" }, "engines": { "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-collect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-fetch": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-4.0.1.tgz", "integrity": "sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", "minizlib": "^3.0.1" }, "engines": { "node": "^18.17.0 || >=20.5.0" }, "optionalDependencies": { "encoding": "^0.1.13" } }, "node_modules/minipass-flush": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, "engines": { "node": ">= 8" } }, "node_modules/minipass-flush/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/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/minipass-pipeline/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/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, "engines": { "node": ">=8" } }, "node_modules/minipass-sized/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/minizlib": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.1.2" }, "engines": { "node": ">= 18" } }, "node_modules/mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/multicast-dns": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", "dev": true, "license": "MIT", "dependencies": { "dns-packet": "^5.2.2", "thunky": "^1.0.2" }, "bin": { "multicast-dns": "cli.js" } }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, "engines": { "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", "dev": true, "license": "MIT", "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "node_modules/node-abi": { "version": "4.26.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.26.0.tgz", "integrity": "sha512-8QwIZqikRvDIkXS2S93LjzhsSPJuIbfaMETWH+Bx8oOT9Sa9UsUtBFQlc3gBNd1+QINjaTloitXr1W3dQLi9Iw==", "dev": true, "license": "MIT", "dependencies": { "semver": "^7.6.3" }, "engines": { "node": ">=22.12.0" } }, "node_modules/node-abi/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/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/node-api-version": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", "dev": true, "license": "MIT", "dependencies": { "semver": "^7.3.5" } }, "node_modules/node-api-version/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/node-forge": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", "integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { "node": ">= 6.13.0" } }, "node_modules/node-gyp": { "version": "11.5.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-11.5.0.tgz", "integrity": "sha512-ra7Kvlhxn5V9Slyus0ygMa2h+UqExPqUIkfk7Pc8QTLT956JLSy51uWFwHtIYy0vI8cB4BDhc/S03+880My/LQ==", "dev": true, "license": "MIT", "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", "graceful-fs": "^4.2.6", "make-fetch-happen": "^14.0.3", "nopt": "^8.0.0", "proc-log": "^5.0.0", "semver": "^7.3.5", "tar": "^7.4.3", "tinyglobby": "^0.2.12", "which": "^5.0.0" }, "bin": { "node-gyp": "bin/node-gyp.js" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-gyp/node_modules/isexe": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" } }, "node_modules/node-gyp/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/node-gyp/node_modules/which": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, "license": "ISC", "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, "node_modules/nopt": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", "dev": true, "license": "ISC", "dependencies": { "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "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", "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/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" }, "funding": { "url": "https://github.com/fb55/nth-check?sponsor=1" } }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "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/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "dev": true, "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, "engines": { "node": ">= 0.8" } }, "node_modules/on-headers": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "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==", "dev": true, "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/open": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", "dev": true, "license": "MIT", "dependencies": { "default-browser": "^5.2.1", "define-lazy-prop": "^3.0.0", "is-inside-container": "^1.0.0", "wsl-utils": "^0.1.0" }, "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, "license": "MIT", "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" }, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "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==", "dev": true, "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, "engines": { "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, "engines": { "node": ">=8" } }, "node_modules/p-map": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "dev": true, "license": "MIT", "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-retry": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", "dev": true, "license": "MIT", "dependencies": { "@types/retry": "0.12.2", "is-network-error": "^1.0.0", "retry": "^0.13.1" }, "engines": { "node": ">=16.17" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-retry/node_modules/retry": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, "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==", "dev": true, "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/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", "dev": true, "license": "MIT", "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/pascal-case": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", "dev": true, "license": "MIT", "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "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-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true, "license": "MIT" }, "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/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true, "license": "MIT" }, "node_modules/pe-library": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", "dev": true, "license": "MIT", "engines": { "node": ">=12", "npm": ">=6" }, "funding": { "type": "github", "url": "https://github.com/sponsors/jet2jet" } }, "node_modules/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/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, "license": "MIT", "engines": { "node": ">=8.6" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, "engines": { "node": ">=8" } }, "node_modules/pkijs": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.3.3.tgz", "integrity": "sha512-+KD8hJtqQMYoTuL1bbGOqxb4z+nZkTAwVdNtWwe8Tc2xNbEmdJYIYoc6Qt0uF55e6YW6KuTHw1DjQ18gMhzepw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@noble/hashes": "1.4.0", "asn1js": "^3.0.6", "bytestreamjs": "^2.0.1", "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" }, "engines": { "node": ">=16.0.0" } }, "node_modules/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/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/postcss/" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, "node_modules/postcss-modules-extract-imports": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", "dev": true, "license": "ISC", "engines": { "node": "^10 || ^12 || >= 14" }, "peerDependencies": { "postcss": "^8.1.0" } }, "node_modules/postcss-modules-local-by-default": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.0.0", "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.1.0" }, "engines": { "node": "^10 || ^12 || >= 14" }, "peerDependencies": { "postcss": "^8.1.0" } }, "node_modules/postcss-modules-scope": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", "dev": true, "license": "ISC", "dependencies": { "postcss-selector-parser": "^7.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" }, "peerDependencies": { "postcss": "^8.1.0" } }, "node_modules/postcss-modules-values": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", "dev": true, "license": "ISC", "dependencies": { "icss-utils": "^5.0.0" }, "engines": { "node": "^10 || ^12 || >= 14" }, "peerDependencies": { "postcss": "^8.1.0" } }, "node_modules/postcss-selector-parser": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" }, "engines": { "node": ">=4" } }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true, "license": "MIT" }, "node_modules/postject": { "version": "1.0.0-alpha.6", "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", "dev": true, "license": "MIT", "optional": true, "peer": true, "dependencies": { "commander": "^9.4.0" }, "bin": { "postject": "dist/cli.js" }, "engines": { "node": ">=14.0.0" } }, "node_modules/postject/node_modules/commander": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", "dev": true, "license": "MIT", "optional": true, "peer": true, "engines": { "node": "^12.20.0 || >=14" } }, "node_modules/pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", "dev": true, "license": "MIT", "dependencies": { "lodash": "^4.17.20", "renderkid": "^3.0.0" } }, "node_modules/proc-log": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-5.0.0.tgz", "integrity": "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==", "dev": true, "license": "ISC", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, "license": "MIT" }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" } }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" }, "engines": { "node": ">=10" } }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" }, "engines": { "node": ">= 0.10" } }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", "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/punycode.js": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pvtsutils": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.8.1" } }, "node_modules/pvutils": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", "dev": true, "license": "MIT", "engines": { "node": ">=16.0.0" } }, "node_modules/qs": { "version": "6.14.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, "engines": { "node": ">=0.6" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "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/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", "dev": true, "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, "node_modules/raw-body/node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" }, "engines": { "node": ">= 0.8" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, "node_modules/raw-body/node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" } }, "node_modules/raw-body/node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/react": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "node_modules/read-binary-file-arch": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.4" }, "bin": { "read-binary-file-arch": "cli.js" } }, "node_modules/readable-stream": { "version": "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", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" }, "engines": { "node": ">= 6" } }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, "engines": { "node": ">=8.10.0" } }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", "dev": true, "license": "MIT", "dependencies": { "resolve": "^1.20.0" }, "engines": { "node": ">= 10.13.0" } }, "node_modules/reflect-metadata": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "dev": true, "license": "Apache-2.0" }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", "dev": true, "license": "MIT", "dependencies": { "css-select": "^4.1.3", "dom-converter": "^0.2.0", "htmlparser2": "^6.1.0", "lodash": "^4.17.21", "strip-ansi": "^6.0.1" } }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true, "license": "MIT" }, "node_modules/resedit": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", "dev": true, "license": "MIT", "dependencies": { "pe-library": "^0.4.1" }, "engines": { "node": ">=12", "npm": ">=6" }, "funding": { "type": "github", "url": "https://github.com/sponsors/jet2jet" } }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "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/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" }, "engines": { "node": ">=8" } }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, "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/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, "license": "MIT", "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" }, "engines": { "node": ">=8" } }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/rimraf": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", "peer": true, "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "node_modules/rimraf/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", "peer": true }, "node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/rimraf/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", "peer": true, "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/rimraf/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", "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, "engines": { "node": "*" } }, "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/run-applescript": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "dev": true, "license": "MIT", "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "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" }, "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.3", "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", "dev": true, "license": "WTFPL OR ISC", "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "node_modules/sax": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" } }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" }, "engines": { "node": ">= 10.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, "node_modules/schema-utils/node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" }, "funding": { "type": "github", "url": "https://github.com/sponsors/epoberezkin" } }, "node_modules/schema-utils/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "node_modules/schema-utils/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", "dev": true, "license": "MIT" }, "node_modules/selfsigned": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-5.5.0.tgz", "integrity": "sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/x509": "^1.14.2", "pkijs": "^3.3.3" }, "engines": { "node": ">=18" } }, "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/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dev": true, "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/send/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "dev": true, "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { "node": ">=4" } }, "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-javascript": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.3.tgz", "integrity": "sha512-h+cZ/XXarqDgCjo+YSyQU/ulDEESGGf8AMK9pPNmhNSl/FzPl6L8pMp1leca5z6NuG6tvV/auC8/43tmovowww==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=20.0.0" } }, "node_modules/serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", "dev": true, "license": "MIT", "dependencies": { "accepts": "~1.3.4", "batch": "0.6.1", "debug": "2.6.9", "escape-html": "~1.0.3", "http-errors": "~1.6.2", "mime-types": "~2.1.17", "parseurl": "~1.3.2" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/serve-index/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/serve-index/node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/serve-index/node_modules/http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", "dev": true, "license": "MIT", "dependencies": { "depd": "~1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.0", "statuses": ">= 1.4.0 < 2" }, "engines": { "node": ">= 0.6" } }, "node_modules/serve-index/node_modules/inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", "dev": true, "license": "ISC" }, "node_modules/serve-index/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true, "license": "MIT" }, "node_modules/serve-index/node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "dev": true, "license": "ISC" }, "node_modules/serve-index/node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/serve-static": { "version": "1.16.2", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, "license": "ISC" }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", "dev": true, "license": "MIT", "dependencies": { "kind-of": "^6.0.2" }, "engines": { "node": ">=8" } }, "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/shell-quote": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel-list": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel-map": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/side-channel-weakmap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "license": "MIT", "dependencies": { "semver": "^7.5.3" }, "engines": { "node": ">=10" } }, "node_modules/simple-update-notifier/node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "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", "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" } }, "node_modules/sockjs": { "version": "0.3.24", "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", "dev": true, "license": "MIT", "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, "node_modules/socks": { "version": "2.8.7", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "dev": true, "license": "MIT", "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" }, "engines": { "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/socks-proxy-agent": { "version": "8.0.5", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "dev": true, "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "socks": "^2.8.3" }, "engines": { "node": ">= 14" } }, "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-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "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/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.0", "handle-thing": "^2.0.0", "http-deceiver": "^1.2.7", "select-hose": "^2.0.0", "spdy-transport": "^3.0.0" }, "engines": { "node": ">=6.0.0" } }, "node_modules/spdy-transport": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "dev": true, "license": "MIT", "dependencies": { "debug": "^4.1.0", "detect-node": "^2.0.4", "hpack.js": "^2.1.6", "obuf": "^1.1.2", "readable-stream": "^3.0.6", "wbuf": "^1.7.3" } }, "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/ssri": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/ssri/-/ssri-12.0.0.tgz", "integrity": "sha512-S7iGNosepx9RadX82oimUkvr0Ct7IjJbEbs4mJcTxst8um95J3sDYU1RBEOvdu6oL1Wek2ODI5i4MAw+dZ6cAQ==", "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.3" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", "dev": true, "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "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", "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/style-loader": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", "dev": true, "license": "MIT", "engines": { "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { "webpack": "^5.27.0" } }, "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/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "dev": true, "license": "MIT", "engines": { "node": ">=6" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" } }, "node_modules/tar": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz", "integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" }, "engines": { "node": ">=18" } }, "node_modules/tar/node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" } }, "node_modules/temp": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "mkdirp": "^0.5.1", "rimraf": "~2.6.2" }, "engines": { "node": ">=6.0.0" } }, "node_modules/temp-file": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", "dev": true, "license": "MIT", "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" } }, "node_modules/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/terser": { "version": "5.46.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" }, "engines": { "node": ">=10" } }, "node_modules/terser-webpack-plugin": { "version": "5.3.16", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", "integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { "node": ">= 10.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { "webpack": "^5.1.0" }, "peerDependenciesMeta": { "@swc/core": { "optional": true }, "esbuild": { "optional": true }, "uglify-js": { "optional": true } } }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, "license": "MIT" }, "node_modules/thingies": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", "dev": true, "license": "MIT", "engines": { "node": ">=10.18" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "^2" } }, "node_modules/thunky": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true, "license": "MIT" }, "node_modules/tiny-async-pool": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", "dev": true, "license": "MIT", "dependencies": { "semver": "^5.5.0" } }, "node_modules/tiny-async-pool/node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver" } }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" }, "funding": { "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tinyglobby/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" }, "peerDependencies": { "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { "picomatch": { "optional": true } } }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "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/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, "engines": { "node": ">=8.0" } }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/tree-dump": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=10.0" }, "funding": { "type": "github", "url": "https://github.com/sponsors/streamich" }, "peerDependencies": { "tslib": "2" } }, "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/ts-loader": { "version": "9.5.4", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4", "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" }, "peerDependencies": { "typescript": "*", "webpack": "^5.0.0" } }, "node_modules/ts-loader/node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" }, "engines": { "node": ">=10" } }, "node_modules/ts-loader/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">= 12" } }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, "license": "0BSD" }, "node_modules/tsyringe": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", "dev": true, "license": "MIT", "dependencies": { "tslib": "^1.9.3" }, "engines": { "node": ">= 6.0.0" } }, "node_modules/tsyringe/node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true, "license": "0BSD" }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true, "license": "(MIT OR CC0-1.0)", "optional": true, "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dev": true, "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" }, "engines": { "node": ">= 0.6" } }, "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/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, "node_modules/unique-filename": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-4.0.0.tgz", "integrity": "sha512-XSnEewXmQ+veP7xX2dS5Q4yZAvO40cBN2MWkJ7D/6sW4Dg6wYBNwM1Vrnz1FhH5AdeLIlUXRI9e28z1YZi71NQ==", "dev": true, "license": "ISC", "dependencies": { "unique-slug": "^5.0.0" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/unique-slug": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-5.0.0.tgz", "integrity": "sha512-9OdaqO5kwqR+1kVgHAhsp5vPNU0hnxRa26rBFNfNgM7M6pNtgzeBn3s/xbyCQL3dcjzOatcef6UUHpB/6MaETg==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" }, "engines": { "node": "^18.17.0 || >=20.5.0" } }, "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/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { "type": "opencollective", "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "bin": { "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.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" }, "node_modules/utila": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", "dev": true, "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" } }, "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/watchpack": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" }, "engines": { "node": ">=10.13.0" } }, "node_modules/wbuf": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "dev": true, "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" } }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, "license": "MIT", "dependencies": { "defaults": "^1.0.3" } }, "node_modules/webpack": { "version": "5.105.3", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.3.tgz", "integrity": "sha512-LLBBA4oLmT7sZdHiYE/PeVuifOxYyE2uL/V+9VQP7YSYdJU7bSf7H8bZRRxW8kEPMkmVjnrXmoR3oejIdX0xbg==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.19.0", "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", "json-parse-even-better-errors": "^2.3.1", "loader-runner": "^4.3.1", "mime-types": "^2.1.27", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.5.1", "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" }, "engines": { "node": ">=10.13.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependenciesMeta": { "webpack-cli": { "optional": true } } }, "node_modules/webpack-cli": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", "dev": true, "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "^0.6.1", "@webpack-cli/configtest": "^3.0.1", "@webpack-cli/info": "^3.0.1", "@webpack-cli/serve": "^3.0.1", "colorette": "^2.0.14", "commander": "^12.1.0", "cross-spawn": "^7.0.3", "envinfo": "^7.14.0", "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^3.1.1", "rechoir": "^0.8.0", "webpack-merge": "^6.0.1" }, "bin": { "webpack-cli": "bin/cli.js" }, "engines": { "node": ">=18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { "webpack": "^5.82.0" }, "peerDependenciesMeta": { "webpack-bundle-analyzer": { "optional": true }, "webpack-dev-server": { "optional": true } } }, "node_modules/webpack-cli/node_modules/commander": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "dev": true, "license": "MIT", "engines": { "node": ">=18" } }, "node_modules/webpack-dev-middleware": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", "dev": true, "license": "MIT", "dependencies": { "colorette": "^2.0.10", "memfs": "^4.43.1", "mime-types": "^3.0.1", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "schema-utils": "^4.0.0" }, "engines": { "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { "optional": true } } }, "node_modules/webpack-dev-middleware/node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/webpack-dev-middleware/node_modules/mime-types": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { "mime-db": "^1.54.0" }, "engines": { "node": ">=18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, "node_modules/webpack-dev-server": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.3.tgz", "integrity": "sha512-9Gyu2F7+bg4Vv+pjbovuYDhHX+mqdqITykfzdM9UyKqKHlsE5aAjRhR+oOEfXW5vBeu8tarzlJFIZva4ZjAdrQ==", "dev": true, "license": "MIT", "dependencies": { "@types/bonjour": "^3.5.13", "@types/connect-history-api-fallback": "^1.5.4", "@types/express": "^4.17.25", "@types/express-serve-static-core": "^4.17.21", "@types/serve-index": "^1.9.4", "@types/serve-static": "^1.15.5", "@types/sockjs": "^0.3.36", "@types/ws": "^8.5.10", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.2.1", "chokidar": "^3.6.0", "colorette": "^2.0.10", "compression": "^1.8.1", "connect-history-api-fallback": "^2.0.0", "express": "^4.22.1", "graceful-fs": "^4.2.6", "http-proxy-middleware": "^2.0.9", "ipaddr.js": "^2.1.0", "launch-editor": "^2.6.1", "open": "^10.0.3", "p-retry": "^6.2.0", "schema-utils": "^4.2.0", "selfsigned": "^5.5.0", "serve-index": "^1.9.1", "sockjs": "^0.3.24", "spdy": "^4.0.2", "webpack-dev-middleware": "^7.4.2", "ws": "^8.18.0" }, "bin": { "webpack-dev-server": "bin/webpack-dev-server.js" }, "engines": { "node": ">= 18.12.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/webpack" }, "peerDependencies": { "webpack": "^5.0.0" }, "peerDependenciesMeta": { "webpack": { "optional": true }, "webpack-cli": { "optional": true } } }, "node_modules/webpack-dev-server/node_modules/ipaddr.js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz", "integrity": "sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==", "dev": true, "license": "MIT", "engines": { "node": ">= 10" } }, "node_modules/webpack-merge": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", "dev": true, "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", "flat": "^5.0.2", "wildcard": "^2.0.1" }, "engines": { "node": ">=18.0.0" } }, "node_modules/webpack-sources": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.4.tgz", "integrity": "sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==", "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" } }, "node_modules/websocket-driver": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", "dev": true, "license": "Apache-2.0", "dependencies": { "http-parser-js": ">=0.5.1", "safe-buffer": ">=5.1.0", "websocket-extensions": ">=0.1.1" }, "engines": { "node": ">=0.8.0" } }, "node_modules/websocket-extensions": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=0.8.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/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true, "license": "MIT" }, "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/ws": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { "optional": true }, "utf-8-validate": { "optional": true } } }, "node_modules/wsl-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", "dev": true, "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" }, "engines": { "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "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/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } } } } lemonade-sdk-lemonade-d88f5d9/src/app/package.json000066400000000000000000000072311515430344400221370ustar00rootroot00000000000000{ "name": "lemonade", "version": "1.0.0", "description": "Lemonade Electron App", "main": "main.js", "scripts": { "start": "npm run build:renderer && electron .", "build:renderer": "webpack --mode development", "build:renderer:prod": "webpack --mode production", "watch:renderer": "webpack --watch --mode development", "dev": "npm run build:renderer && electron . --remote-debugging-port=9222", "build": "npm run build:renderer:prod && electron-builder", "build:win": "npm run build:renderer:prod && electron-builder --win", "build:mac": "npm run build:renderer:prod && electron-builder --mac", "build:linux": "npm run build:renderer:prod && electron-builder --linux", "build:appimage": "npm run build:renderer:prod && electron-builder --linux AppImage" }, "keywords": [ "electron", "lemonade", "typescript" ], "author": "", "license": "MIT", "devDependencies": { "@types/katex": "^0.16.7", "@types/node": "^20.10.5", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "css-loader": "^7.1.4", "electron": "^39.2.4", "electron-builder": "^26.8.1", "glob": ">=10.5.0", "html-webpack-plugin": "^5.6.6", "node-forge": ">=1.3.2", "style-loader": "^4.0.0", "ts-loader": "^9.5.4", "typescript": "^5.3.3", "webpack": "^5.105.3", "webpack-cli": "^6.0.1", "webpack-dev-server": "^5.2.3" }, "dependencies": { "@types/markdown-it": "^14.1.2", "eventsource": "^4.1.0", "highlight.js": "^11.11.1", "katex": "^0.16.25", "markdown-it": "^14.1.0", "markdown-it-texmath": "^1.0.0", "react": "^19.2.0", "react-dom": "^19.2.0" }, "overrides": { "serialize-javascript": "^7.0.3" }, "build": { "appId": "com.lemonade.app", "productName": "Lemonade", "publish": null, "toolsets": { "appimage": "1.0.2" }, "directories": { "output": "dist-app" }, "files": [ "main.js", "preload.js", { "from": "dist/renderer", "to": "dist/renderer", "filter": [ "**/*", "!**/*.map" ] }, "!node_modules", "!src", "!**/*.ts", "!**/*.tsx", "!**/*.map" ], "extraResources": [], "asarUnpack": [], "win": { "icon": "../../docs/assets/favicon.ico", "target": [ { "target": "nsis", "arch": [ "x64" ] } ] }, "electronLanguages": [ "en-US" ], "mac": { "icon": "../../docs/assets/logo_512.png", "target": [ "dmg" ], "category": "public.app-category.business", "hardenedRuntime": true, "gatekeeperAssess": false, "entitlements": "../../src/cpp/server/entitlements.plist", "entitlementsInherit": "../../src/cpp/server/entitlements.plist" }, "linux": { "target": [ "dir" ], "category": "Utility", "icon": "../../src/app/assets/logo.svg", "desktop": { "entry": { "Name": "Lemonade App", "Comment": "Local LLMs with GPU and NPU acceleration - Desktop Application", "GenericName": "AI Model Manager", "Keywords": "AI;LLM;GPU;NPU;Machine Learning;", "Categories": "Development;Utility;" } } }, "appImage": { "artifactName": "${productName}-${version}-${arch}.${ext}", "synopsis": "Local LLM server with GPU and NPU acceleration", "category": "Development" }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": true, "createStartMenuShortcut": true } } } lemonade-sdk-lemonade-d88f5d9/src/app/preload.js000066400000000000000000000051771515430344400216440ustar00rootroot00000000000000const { contextBridge, ipcRenderer } = require('electron'); contextBridge.exposeInMainWorld('api', { writeClipboard: (text) => ipcRenderer.invoke('write-clipboard', text), // Expose any APIs you need here isWebApp: false, // Explicit flag to indicate Electron mode (vs web) platform: process.platform, minimizeWindow: () => ipcRenderer.send('minimize-window'), maximizeWindow: () => ipcRenderer.send('maximize-window'), closeWindow: () => ipcRenderer.send('close-window'), openExternal: (url) => ipcRenderer.send('open-external', url), onMaximizeChange: (callback) => { ipcRenderer.on('maximize-change', (event, isMaximized) => callback(isMaximized)); }, updateMinWidth: (width) => ipcRenderer.send('update-min-width', width), zoomIn: () => ipcRenderer.send('zoom-in'), zoomOut: () => ipcRenderer.send('zoom-out'), getSettings: () => ipcRenderer.invoke('get-app-settings'), saveSettings: (settings) => ipcRenderer.invoke('save-app-settings', settings), onSettingsUpdated: (callback) => { if (typeof callback !== 'function') { return undefined; } const channel = 'settings-updated'; const handler = (_event, payload) => { callback(payload); }; ipcRenderer.on(channel, handler); return () => { ipcRenderer.removeListener(channel, handler); }; }, getVersion: () => ipcRenderer.invoke('get-version'), discoverServerPort: () => ipcRenderer.invoke('discover-server-port'), getServerPort: () => ipcRenderer.invoke('get-server-port'), // Returns the configured server base URL, or null if using localhost discovery getServerBaseUrl: () => ipcRenderer.invoke('get-server-base-url'), getServerAPIKey: () => ipcRenderer.invoke('get-server-api-key'), onServerPortUpdated: (callback) => { if (typeof callback !== 'function') { return undefined; } const channel = 'server-port-updated'; const handler = (_event, port) => { callback(port); }; ipcRenderer.on(channel, handler); return () => { ipcRenderer.removeListener(channel, handler); }; }, onConnectionSettingsUpdated: (callback) => { if (typeof callback !== 'function') { return undefined; } const channel = 'connection-settings-updated'; const handler = (_event, baseURL, apiKey) => { callback(baseURL, apiKey); }; ipcRenderer.on(channel, handler); return () => { ipcRenderer.removeListener(channel, handler); }; }, getSystemStats: () => ipcRenderer.invoke('get-system-stats'), getSystemInfo: () => ipcRenderer.invoke('get-system-info'), getLocalMarketplaceUrl: () => ipcRenderer.invoke('get-local-marketplace-url'), }); lemonade-sdk-lemonade-d88f5d9/src/app/src/000077500000000000000000000000001515430344400204355ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/src/app/src/global.d.ts000066400000000000000000000042471515430344400224760ustar00rootroot00000000000000import type { AppSettings } from './renderer/utils/appSettings'; declare module '*.svg' { const content: string; export default content; } declare module '../../assets/*.svg' { const content: string; export default content; } declare module 'markdown-it-texmath' { import MarkdownIt from 'markdown-it'; interface TexmathOptions { engine?: any; delimiters?: 'dollars' | 'brackets' | 'gitlab' | 'kramdown'; katexOptions?: any; } function texmath(md: MarkdownIt, options?: TexmathOptions): void; export = texmath; } declare global { interface Window { api: { writeClipboard?: (text: string) => Promise; isWebApp?: boolean; // Explicit flag to indicate web mode (vs Electron) platform: string; minimizeWindow: () => void; maximizeWindow: () => void; closeWindow: () => void; openExternal: (url: string) => void; onMaximizeChange: (callback: (isMaximized: boolean) => void) => void; updateMinWidth: (width: number) => void; zoomIn: () => void; zoomOut: () => void; getSettings?: () => Promise; saveSettings?: (settings: AppSettings) => Promise; onSettingsUpdated?: (callback: (settings: AppSettings) => void) => void | (() => void); getVersion?: () => Promise; discoverServerPort?: () => Promise; getServerPort?: () => Promise; // Returns the configured server base URL or null if using localhost discovery getServerBaseUrl?: () => Promise; getServerAPIKey?: () => Promise; onServerPortUpdated?: (callback: (port: number) => void) => void | (() => void); onConnectionSettingsUpdated?: (callback: (baseURL: string, apiKey: string) => void) => void | (() => void); getSystemStats?: () => Promise<{ cpu_percent: number | null; memory_gb: number; gpu_percent: number | null; vram_gb: number | null; npu_percent: number | null }>; getSystemInfo?: () => Promise<{ system: string; os: string; cpu: string; gpus: string[]; gtt_gb?: string; vram_gb?: string }>; getLocalMarketplaceUrl?: () => Promise; }; } } export {}; lemonade-sdk-lemonade-d88f5d9/src/app/src/renderer/000077500000000000000000000000001515430344400222435ustar00rootroot00000000000000lemonade-sdk-lemonade-d88f5d9/src/app/src/renderer/AboutModal.tsx000066400000000000000000000150201515430344400250300ustar00rootroot00000000000000import React, { useEffect, useRef, useState } from 'react'; import { fetchSystemInfoData } from './utils/systemData'; interface AboutModalProps { isOpen: boolean; onClose: () => void; } interface SystemInfo { system: string; os: string; cpu: string; gpus: string[]; gtt_gb?: string; vram_gb?: string; } const AboutModal: React.FC = ({ isOpen, onClose }) => { const [version, setVersion] = useState('Loading...'); const [systemInfo, setSystemInfo] = useState(null); const [isLoadingInfo, setIsLoadingInfo] = useState(false); const cardRef = useRef(null); useEffect(() => { if (isOpen && window.api?.getVersion) { setVersion('Loading...'); setIsLoadingInfo(true); // Retry logic to handle backend startup delay const fetchVersionWithRetry = async (retries = 3, delay = 1000) => { for (let i = 0; i < retries; i++) { const v = await window.api.getVersion!(); if (v !== 'Unknown') { setVersion(v); return; } if (i < retries - 1) { await new Promise(resolve => setTimeout(resolve, delay)); } } setVersion('Unknown (Backend not running)'); }; const fetchSystemInfo = async () => { try { if (window.api?.getSystemInfo) { const info = await window.api.getSystemInfo(); console.log('SystemInfo received in AboutModal:', info); setSystemInfo(info); return; } const { info } = await fetchSystemInfoData(); if (!info) { return; } const gpus: string[] = []; let maxGttGb = 0; let maxVramGb = 0; const considerAmdGpu = (gpu?: { name?: string; virtual_mem_gb?: number; vram_gb?: number }) => { if (!gpu) return; if (gpu.name) gpus.push(gpu.name); if (typeof gpu.virtual_mem_gb === 'number' && isFinite(gpu.virtual_mem_gb)) { maxGttGb = Math.max(maxGttGb, gpu.virtual_mem_gb); } if (typeof gpu.vram_gb === 'number' && isFinite(gpu.vram_gb)) { maxVramGb = Math.max(maxVramGb, gpu.vram_gb); } }; considerAmdGpu(info.devices?.amd_igpu); info.devices?.amd_dgpu?.forEach(considerAmdGpu); info.devices?.nvidia_dgpu?.forEach((gpu) => { if (gpu?.name) gpus.push(gpu.name); }); const normalized: SystemInfo = { system: 'Unknown', os: info.os_version || 'Unknown', cpu: info.processor || 'Unknown', gpus, gtt_gb: maxGttGb > 0 ? `${maxGttGb} GB` : 'Unknown', vram_gb: maxVramGb > 0 ? `${maxVramGb} GB` : 'Unknown', }; setSystemInfo(normalized); } catch (error) { console.error('Failed to fetch system info:', error); } finally { setIsLoadingInfo(false); } }; fetchVersionWithRetry(); fetchSystemInfo(); } }, [isOpen]); useEffect(() => { if (!isOpen) return; const handleClickOutside = (event: MouseEvent) => { if (cardRef.current && !cardRef.current.contains(event.target as Node)) { onClose(); } }; const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { onClose(); } }; document.addEventListener('mousedown', handleClickOutside); document.addEventListener('keydown', handleKeyDown); return () => { document.removeEventListener('mousedown', handleClickOutside); document.removeEventListener('keydown', handleKeyDown); }; }, [isOpen, onClose]); if (!isOpen) return null; return (

Lemonade

Local AI control center

Version {version}
{!isLoadingInfo && systemInfo && ( <> {systemInfo.system && systemInfo.system !== 'Unknown' && (
System {systemInfo.system}
)} {systemInfo.os && systemInfo.os !== 'Unknown' && (
OS {systemInfo.os}
)} {systemInfo.cpu && systemInfo.cpu !== 'Unknown' && (
CPU {systemInfo.cpu}
)} {systemInfo.gpus.length > 0 && (
GPU{systemInfo.gpus.length > 1 ? 's' : ''} {systemInfo.gpus.join(', ')}
)} {systemInfo.gtt_gb && systemInfo.gtt_gb !== 'Unknown' && (
Shared GPU memory {systemInfo.gtt_gb}
)} {systemInfo.vram_gb && systemInfo.vram_gb !== 'Unknown' && (
Dedicated GPU memory {systemInfo.vram_gb}
)} )}
); }; export default AboutModal; lemonade-sdk-lemonade-d88f5d9/src/app/src/renderer/App.tsx000066400000000000000000000322461515430344400235320ustar00rootroot00000000000000import React, { useState, useEffect, useRef, useCallback } from 'react'; import { ChevronLeft } from './components/Icons'; import TitleBar from './TitleBar'; import ChatWindow from './ChatWindow'; import ModelManager, { LeftPanelView } from './ModelManager'; import LogsWindow from './LogsWindow'; import ResizableDivider from './ResizableDivider'; import DownloadManager from './DownloadManager'; import StatusBar from './StatusBar'; import { ModelsProvider } from './hooks/useModels'; import { SystemProvider } from './hooks/useSystem'; import { DEFAULT_LAYOUT_SETTINGS } from './utils/appSettings'; import '../../styles.css'; const LAYOUT_CONSTANTS = { modelManagerMinWidth: 200, experienceRailWidth: 40, mainContentMinWidth: 300, chatMinWidth: 250, dividerWidth: 4, absoluteMinWidth: 400, }; // Inner component that can use SystemProvider context const AppContent: React.FC = () => { const [isChatVisible, setIsChatVisible] = useState(DEFAULT_LAYOUT_SETTINGS.isChatVisible); const [isModelManagerVisible, setIsModelManagerVisible] = useState(DEFAULT_LAYOUT_SETTINGS.isModelManagerVisible); const [leftPanelView, setLeftPanelView] = useState('models'); const [externalContentUrl, setExternalContentUrl] = useState(null); const [isLogsVisible, setIsLogsVisible] = useState(DEFAULT_LAYOUT_SETTINGS.isLogsVisible); const [isDownloadManagerVisible, setIsDownloadManagerVisible] = useState(false); const [modelManagerWidth, setModelManagerWidth] = useState(DEFAULT_LAYOUT_SETTINGS.modelManagerWidth); const [chatWidth, setChatWidth] = useState(DEFAULT_LAYOUT_SETTINGS.chatWidth); const [logsHeight, setLogsHeight] = useState(DEFAULT_LAYOUT_SETTINGS.logsHeight); const [layoutLoaded, setLayoutLoaded] = useState(false); const isDraggingRef = useRef<'left' | 'right' | 'bottom' | null>(null); const startXRef = useRef(0); const startYRef = useRef(0); const startWidthRef = useRef(0); const startHeightRef = useRef(0); // Load saved layout settings on mount useEffect(() => { const loadLayoutSettings = async () => { try { if (window?.api?.getSettings) { const settings = await window.api.getSettings(); if (settings.layout) { setIsChatVisible(settings.layout.isChatVisible ?? DEFAULT_LAYOUT_SETTINGS.isChatVisible); setIsModelManagerVisible(settings.layout.isModelManagerVisible ?? DEFAULT_LAYOUT_SETTINGS.isModelManagerVisible); const savedView = settings.layout.leftPanelView; if (savedView === 'models' || savedView === 'marketplace' || savedView === 'backends' || savedView === 'settings') { setLeftPanelView(savedView); } setIsLogsVisible(settings.layout.isLogsVisible ?? DEFAULT_LAYOUT_SETTINGS.isLogsVisible); setModelManagerWidth(settings.layout.modelManagerWidth ?? DEFAULT_LAYOUT_SETTINGS.modelManagerWidth); setChatWidth(settings.layout.chatWidth ?? DEFAULT_LAYOUT_SETTINGS.chatWidth); setLogsHeight(settings.layout.logsHeight ?? DEFAULT_LAYOUT_SETTINGS.logsHeight); } } } catch (error) { console.error('Failed to load layout settings:', error); } finally { // Override with URL parameters if present const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('logs') === 'true') { setIsLogsVisible(true); } setLayoutLoaded(true); } }; loadLayoutSettings(); }, []); // Save layout settings when they change (debounced) const saveLayoutSettings = useCallback(async () => { if (!layoutLoaded) return; try { if (window?.api?.getSettings && window?.api?.saveSettings) { // Get current settings and merge layout changes const currentSettings = await window.api.getSettings(); await window.api.saveSettings({ ...currentSettings, layout: { isChatVisible, isModelManagerVisible, leftPanelView, isLogsVisible, modelManagerWidth, chatWidth, logsHeight, }, }); } } catch (error) { console.error('Failed to save layout settings:', error); } }, [layoutLoaded, isChatVisible, isModelManagerVisible, leftPanelView, isLogsVisible, modelManagerWidth, chatWidth, logsHeight]); // Debounced save effect useEffect(() => { if (!layoutLoaded) return; const timeoutId = setTimeout(saveLayoutSettings, 300); return () => clearTimeout(timeoutId); }, [saveLayoutSettings, layoutLoaded]); // Listen for download start events to automatically open download manager // and download completion events from chat to close it useEffect(() => { const handleDownloadStart = () => { setIsDownloadManagerVisible(true); }; // When a chat-initiated download completes, minimize the download manager const handleChatDownloadComplete = () => { setIsDownloadManagerVisible(false); }; window.addEventListener('download:started' as any, handleDownloadStart); window.addEventListener('download:chatComplete' as any, handleChatDownloadComplete); const handleOpenExternalContent = (e: any) => { if (e.detail?.url) { setExternalContentUrl(e.detail.url); setIsChatVisible(true); setIsDownloadManagerVisible(false); } }; window.addEventListener('open-external-content' as any, handleOpenExternalContent); return () => { window.removeEventListener('download:started' as any, handleDownloadStart); window.removeEventListener('download:chatComplete' as any, handleChatDownloadComplete); window.removeEventListener('open-external-content' as any, handleOpenExternalContent); }; }, []); useEffect(() => { const handleExperienceModeChanged = (event: Event) => { const customEvent = event as CustomEvent<{ active?: boolean }>; if (customEvent.detail?.active) { setIsModelManagerVisible(false); } }; window.addEventListener('experienceModeChanged' as any, handleExperienceModeChanged); return () => { window.removeEventListener('experienceModeChanged' as any, handleExperienceModeChanged); }; }, []); useEffect(() => { const hasMainColumn = isLogsVisible; let computedMinWidth = LAYOUT_CONSTANTS.experienceRailWidth; // Rail always visible if (isModelManagerVisible) { computedMinWidth += LAYOUT_CONSTANTS.modelManagerMinWidth; } if (hasMainColumn) { computedMinWidth += LAYOUT_CONSTANTS.mainContentMinWidth; } if (isChatVisible) { computedMinWidth += LAYOUT_CONSTANTS.chatMinWidth; } let dividerCount = 0; if (isModelManagerVisible && (hasMainColumn || isChatVisible)) { dividerCount += 1; } if (hasMainColumn && isChatVisible) { dividerCount += 1; } computedMinWidth += dividerCount * LAYOUT_CONSTANTS.dividerWidth; const targetWidth = Math.max(computedMinWidth, LAYOUT_CONSTANTS.absoluteMinWidth); if (window?.api?.updateMinWidth) { window.api.updateMinWidth(targetWidth); } }, [isModelManagerVisible, isLogsVisible, isChatVisible]); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { if (!isDraggingRef.current) return; if (isDraggingRef.current === 'left') { // Dragging left divider (model manager) const delta = e.clientX - startXRef.current; const newWidth = Math.max(200, Math.min(500, startWidthRef.current + delta)); setModelManagerWidth(newWidth); } else if (isDraggingRef.current === 'right') { // Dragging right divider (chat window) // For right-side panel: drag left = increase width, drag right = decrease width const delta = startXRef.current - e.clientX; // Base chat width from drag let proposedWidth = startWidthRef.current + delta; // Compute maximum allowed width so the center panel (welcome screen/logs) // can respect its minimum width and the layout doesn't overflow horizontally. const appLayout = document.querySelector('.app-layout') as HTMLElement | null; const appWidth = appLayout?.clientWidth || window.innerWidth; const leftPanelWidth = isModelManagerVisible ? modelManagerWidth + LAYOUT_CONSTANTS.experienceRailWidth : LAYOUT_CONSTANTS.experienceRailWidth; const hasCenterColumn = isLogsVisible; const minCenterWidth = hasCenterColumn ? 300 : 0; // keep in sync with CSS min-width // Account for vertical dividers (each 4px wide) const dividerCount = ((isModelManagerVisible && (hasCenterColumn || isChatVisible)) ? 1 : 0) + ((hasCenterColumn && isChatVisible) ? 1 : 0); const dividerSpace = dividerCount * 4; const maxWidthFromLayout = appWidth - leftPanelWidth - minCenterWidth - dividerSpace; const minChatWidth = 250; const maxChatWidth = Math.max( minChatWidth, Math.min(800, isFinite(maxWidthFromLayout) ? maxWidthFromLayout : 800) ); const newWidth = Math.max(minChatWidth, Math.min(maxChatWidth, proposedWidth)); setChatWidth(newWidth); } else if (isDraggingRef.current === 'bottom') { // Dragging bottom divider (logs window) const delta = startYRef.current - e.clientY; const newHeight = Math.max(100, Math.min(400, startHeightRef.current + delta)); setLogsHeight(newHeight); } }; const handleMouseUp = () => { isDraggingRef.current = null; document.body.style.cursor = ''; document.body.style.userSelect = ''; }; document.addEventListener('mousemove', handleMouseMove); document.addEventListener('mouseup', handleMouseUp); return () => { document.removeEventListener('mousemove', handleMouseMove); document.removeEventListener('mouseup', handleMouseUp); }; }, [ chatWidth, isChatVisible, isModelManagerVisible, isLogsVisible, modelManagerWidth, logsHeight, ]); const handleLeftDividerMouseDown = (e: React.MouseEvent) => { isDraggingRef.current = 'left'; startXRef.current = e.clientX; startWidthRef.current = modelManagerWidth; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }; const handleRightDividerMouseDown = (e: React.MouseEvent) => { isDraggingRef.current = 'right'; startXRef.current = e.clientX; startWidthRef.current = chatWidth; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; }; const handleCloseDownloadManager = useCallback(() => { setIsDownloadManagerVisible(false); }, []); return ( setIsChatVisible(!isChatVisible)} isModelManagerVisible={isModelManagerVisible} onToggleModelManager={() => setIsModelManagerVisible(!isModelManagerVisible)} isLogsVisible={isLogsVisible} onToggleLogs={() => setIsLogsVisible(!isLogsVisible)} isDownloadManagerVisible={isDownloadManagerVisible} onToggleDownloadManager={() => setIsDownloadManagerVisible(!isDownloadManagerVisible)} />
{isModelManagerVisible && (isLogsVisible || isChatVisible) && ( )} {isLogsVisible && (
)} {isChatVisible && ( <> {isLogsVisible && ( )} {externalContentUrl ? (