commit 1d427f4cf58ceaaa0fd0901d23596422e7855c6c Author: Gemini AI Date: Fri Dec 26 11:27:03 2025 +0400 v0.5.0: NomadArch - Binary-Free Mode Release Features: - Binary-Free Mode: No OpenCode binary required - NomadArch Native mode with free Zen models - Native session management - Provider routing (Zen, Qwen, Z.AI) - Fixed MCP connection with explicit connectAll() - Updated installers and launchers for all platforms - UI binary selector with Native option Free Models Available: - GPT-5 Nano (400K context) - Grok Code Fast 1 (256K context) - GLM-4.7 (205K context) - Doubao Seed Code (256K context) - Big Pickle (200K context) diff --git a/.backup/Install-Linux.sh.backup b/.backup/Install-Linux.sh.backup new file mode 100644 index 0000000..b01ca2c --- /dev/null +++ b/.backup/Install-Linux.sh.backup @@ -0,0 +1,312 @@ +#!/bin/bash + +echo "" +echo " ███╗ ██╗ ██████╗ ███╗ ███╗ █████╗ ██████╗ █████╗ ██████╗ ██████╗██╗ ██╗" +echo " ████╗ ██║██╔═══██╗████╗ ████║██╔══██╗██╔══██╗██╔══██╗██╔════╝██║ ██║" +echo " ██╔██╗ ██║██║ ██║██╔████╔██║███████║██║ ██║███████║██████╔╝██║ ███████║" +echo " ██║╚██╗██║██║ ██║██║╚██╔╝██║██╔══██║██║ ██║██╔══██║██╔══██╗██║ ██╔══██║" +echo " ██║ ╚████║╚██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝██║ ██║██║ ██║╚██████╗██║ ██║" +echo " ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝" +echo "" +echo " INSTALLER - Enhanced with Auto-Dependency Resolution" +echo " ═════════════════════════════════════════════════════════════════════════════" +echo "" + +ERRORS=0 +WARNINGS=0 + +cd "$(dirname "$0")" + +echo "[STEP 1/6] Detecting Linux Distribution..." +echo "" + +# Detect Linux distribution +if [ -f /etc/os-release ]; then + . /etc/os-release + DISTRO=$ID + DISTRO_VERSION=$VERSION_ID + echo "[OK] Detected: $PRETTY_NAME" +else + echo "[WARN] Could not detect specific distribution" + DISTRO="unknown" + WARNINGS=$((WARNINGS + 1)) +fi + +echo "" +echo "[STEP 2/6] Checking System Requirements..." +echo "" + +# Check for Node.js +echo "[INFO] Checking Node.js..." +if ! command -v node &> /dev/null; then + echo "[ERROR] Node.js not found!" + echo "" + echo "NomadArch requires Node.js to run." + echo "" + echo "Install using your package manager:" + if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then + echo " sudo apt update && sudo apt install -y nodejs npm" + elif [ "$DISTRO" = "fedora" ]; then + echo " sudo dnf install -y nodejs npm" + elif [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "manjaro" ]; then + echo " sudo pacman -S nodejs npm" + elif [ "$DISTRO" = "opensuse-leap" ] || [ "$DISTRO" = "opensuse-tumbleweed" ]; then + echo " sudo zypper install -y nodejs npm" + else + echo " Visit https://nodejs.org/ for installation instructions" + fi + echo "" + echo "Or install Node.js using NVM (Node Version Manager):" + echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash" + echo " source ~/.bashrc" + echo " nvm install 20" + echo "" + exit 1 +fi +NODE_VERSION=$(node --version) +echo "[OK] Node.js detected: $NODE_VERSION" + +# Check Node.js version (require 18+) +NODE_MAJOR=$(echo $NODE_VERSION | cut -d. -f1 | sed 's/v//') +if [ "$NODE_MAJOR" -lt 18 ]; then + echo "[WARN] Node.js version is too old (found v$NODE_VERSION, required 18+)" + echo "[INFO] Please update Node.js" + WARNINGS=$((WARNINGS + 1)) +fi + +# Check for npm +echo "[INFO] Checking npm..." +if ! command -v npm &> /dev/null; then + echo "[ERROR] npm not found! This should come with Node.js." + echo "Please reinstall Node.js" + ERRORS=$((ERRORS + 1)) +fi +NPM_VERSION=$(npm --version) +echo "[OK] npm detected: $NPM_VERSION" + +# Check for build-essential (required for native modules) +echo "[INFO] Checking build tools..." +if ! command -v make &> /dev/null || ! command -v gcc &> /dev/null || ! command -v g++ &> /dev/null; then + echo "[WARN] Build tools not found (gcc, g++, make)" + echo "[INFO] Installing build-essential..." + if [ "$DISTRO" = "ubuntu" ] || [ "$DISTRO" = "debian" ]; then + sudo apt update && sudo apt install -y build-essential + elif [ "$DISTRO" = "fedora" ]; then + sudo dnf install -y gcc g++ make + elif [ "$DISTRO" = "arch" ] || [ "$DISTRO" = "manjaro" ]; then + sudo pacman -S --noconfirm base-devel + elif [ "$DISTRO" = "opensuse-leap" ] || [ "$DISTRO" = "opensuse-tumbleweed" ]; then + sudo zypper install -y gcc-c++ make + else + echo "[WARN] Could not auto-install build tools. Please install manually." + WARNINGS=$((WARNINGS + 1)) + fi +else + echo "[OK] Build tools detected" +fi + +# Check for Git (optional but recommended) +echo "[INFO] Checking Git..." +if ! command -v git &> /dev/null; then + echo "[WARN] Git not found (optional but recommended)" + echo "[INFO] Install: sudo apt install git (or equivalent for your distro)" + WARNINGS=$((WARNINGS + 1)) +else + GIT_VERSION=$(git --version) + echo "[OK] Git detected: $GIT_VERSION" +fi + +# Check for Python (optional, for some tools) +echo "[INFO] Checking Python..." +if command -v python3 &> /dev/null; then + PY_VERSION=$(python3 --version 2>&1 | awk '{print $2}') + echo "[OK] Python3 detected: $PY_VERSION" +elif command -v python &> /dev/null; then + PY_VERSION=$(python --version 2>&1 | awk '{print $2}') + echo "[OK] Python detected: $PY_VERSION" +else + echo "[WARN] Python not found (optional, required for some build tools)" + WARNINGS=$((WARNINGS + 1)) +fi + +# Check disk space (at least 2GB free) +FREE_SPACE=$(df -BG "$PWD" | tail -1 | awk '{print int($4/1024/1024)}') +if [ "$FREE_SPACE" -lt 2048 ]; then + echo "[WARN] Low disk space ($FREE_SPACE MB free, recommended 2GB+)" + WARNINGS=$((WARNINGS + 1)) +else + echo "[OK] Disk space: $FREE_SPACE MB free" +fi + +echo "" +echo "[STEP 3/7] Downloading OpenCode Binary..." +echo "" + +if [ ! -d "bin" ]; then + mkdir bin +fi + +if [ ! -f "bin/opencode" ]; then + echo "[SETUP] Downloading opencode binary from GitHub releases..." + echo "[INFO] This is required for workspace functionality." + + # Detect architecture + ARCH=$(uname -m) + if [ "$ARCH" = "x86_64" ]; then + FILENAME="opencode-linux" + elif [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then + FILENAME="opencode-linux-arm64" + else + echo "[WARN] Unsupported architecture: $ARCH" + echo "[INFO] Please download opencode manually from: https://opencode.ai/" + WARNINGS=$((WARNINGS + 1)) + FILENAME="" + fi + + if [ -n "$FILENAME" ]; then + curl -L -o "bin/opencode" "https://github.com/NeuralNomadsAI/NomadArch/releases/latest/download/$FILENAME" + if [ $? -ne 0 ]; then + echo "[WARN] Failed to download opencode automatically." + echo "[INFO] You can install OpenCode CLI manually from: https://opencode.ai/" + echo "[INFO] Or download opencode and place it in bin/ folder" + echo "[INFO] Without opencode, workspace creation will fail." + WARNINGS=$((WARNINGS + 1)) + else + chmod +x bin/opencode + echo "[OK] opencode downloaded successfully" + fi + fi +else + echo "[OK] opencode already exists" +fi + +echo "" +echo "[STEP 5/7] Setting Permissions..." +echo "" + +# Make scripts executable +chmod +x Launch-Unix.sh 2>/dev/null +chmod +x Install-Linux.sh 2>/dev/null +chmod +x Install-Mac.sh 2>/dev/null + +echo "[OK] Scripts permissions set" + +echo "" +echo "[STEP 6/7] Cleaning Previous Installation..." +echo "" + +if [ -d "node_modules" ]; then + echo "[INFO] Found existing node_modules, cleaning..." + rm -rf node_modules + echo "[OK] Cleaned previous installation artifacts" +else + echo "[OK] No previous installation found" +fi + +echo "" +echo "[STEP 7/7] Installing Dependencies..." +echo "" +echo "This may take 3-10 minutes depending on your internet speed." +echo "Please be patient and do not close this terminal." +echo "" + +npm install +if [ $? -ne 0 ]; then + echo "" + echo "[ERROR] npm install failed!" + echo "" + echo "Common solutions:" + echo " 1. Check your internet connection" + echo " 2. Try running with sudo if permission errors occur" + echo " 3. Clear npm cache: npm cache clean --force" + echo " 4. Delete node_modules and try again" + echo "" + echo "Attempting to clear npm cache and retry..." + npm cache clean --force + echo "Retrying installation..." + npm install + if [ $? -ne 0 ]; then + echo "[ERROR] Installation failed after retry." + ERRORS=$((ERRORS + 1)) + fi +else + echo "[OK] Dependencies installed successfully" +fi + +echo "" +echo "[STEP 6/6] Building NomadArch..." +echo "" +echo "This may take 2-5 minutes depending on your system." +echo "" + +npm run build +if [ $? -ne 0 ]; then + echo "" + echo "[ERROR] Build failed!" + echo "" + echo "Common solutions:" + echo " 1. Check that Node.js version is 18+ (node --version)" + echo " 2. Clear npm cache: npm cache clean --force" + echo " 3. Delete node_modules and reinstall: rm -rf node_modules && npm install" + echo " 4. Check for missing system dependencies (build-essential)" + echo " 5. Check error messages above for specific issues" + echo "" + ERRORS=$((ERRORS + 1)) +else + echo "[OK] Build completed successfully" +fi + +echo "" +echo "Verifying Installation" +echo "" + +# Check if opencode binary exists +if [ ! -f "bin/opencode" ]; then + echo "[WARN] opencode binary not found. Workspace creation will fail." + echo "[INFO] Download from: https://github.com/NeuralNomadsAI/NomadArch/releases/latest/download/opencode-linux" + echo "[INFO] Or install OpenCode CLI from: https://opencode.ai/" + WARNINGS=$((WARNINGS + 1)) +else + echo "[OK] opencode binary verified" +fi + +echo "" +echo "Installation Summary" +echo "" + +if [ $ERRORS -gt 0 ]; then + echo "" + echo "════════════════════════════════════════════════════════════════════════════" + echo "[FAILED] Installation encountered $ERRORS error(s)!" + echo "" + echo "Please review error messages above and try again." + echo "For help, see: https://github.com/NeuralNomadsAI/NomadArch/issues" + echo "════════════════════════════════════════════════════════════════════════════" + echo "" + exit 1 +fi + +echo "" +echo "════════════════════════════════════════════════════════════════════════════" +echo "[SUCCESS] Installation Complete!" +echo "" + +if [ $WARNINGS -gt 0 ]; then + echo "[WARN] There were $WARNINGS warning(s) during installation." + echo "Review warnings above. Most warnings are non-critical." + echo "" +fi + +echo "You can now run NomadArch using:" +echo " ./Launch-Unix.sh" +echo "" +echo "For help and documentation, see: README.md" +echo "For troubleshooting, see: TROUBLESHOOTING.md" +echo "════════════════════════════════════════════════════════════════════════════" +echo "" +echo "Press Enter to start NomadArch now, or Ctrl+C to start later..." +read + +echo "" +echo "[INFO] Starting NomadArch..." +./Launch-Unix.sh diff --git a/.backup/Install-Mac.sh.backup b/.backup/Install-Mac.sh.backup new file mode 100644 index 0000000..d006949 --- /dev/null +++ b/.backup/Install-Mac.sh.backup @@ -0,0 +1,349 @@ +#!/bin/bash + +echo "" +echo " ███╗ ██╗ ██████╗ ███╗ ███╗ █████╗ ██████╗ █████╗ ██████╗ ██████╗██╗ ██╗" +echo " ████╗ ██║██╔═══██╗████╗ ████║██╔══██╗██╔══██╗██╔══██╗██╔════╝██║ ██║" +echo " ██╔██╗ ██║██║ ██║██╔████╔██║███████║██║ ██║███████║██████╔╝██║ ███████║" +echo " ██║╚██╗██║██║ ██║██║╚██╔╝██║██╔══██║██║ ██║██╔══██║██╔══██╗██║ ██╔══██║" +echo " ██║ ╚████║╚██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝██║ ██║██║ ██║╚██████╗██║ ██║" +echo " ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝" +echo "" +echo " INSTALLER - macOS Enhanced with Auto-Dependency Resolution" +echo " ═══════════════════════════════════════════════════════════════════════════" +echo "" + +ERRORS=0 +WARNINGS=0 + +cd "$(dirname "$0")" + +echo "[STEP 1/7] Checking macOS Version..." +echo "" + +# Detect macOS version +if [ -f /System/Library/CoreServices/SystemVersion.plist ]; then + MAC_VERSION=$(defaults read /System/Library/CoreServices/SystemVersion.plist ProductVersion) + MAC_MAJOR=$(echo $MAC_VERSION | cut -d. -f1) + echo "[OK] macOS detected: $MAC_VERSION" + + # Check minimum version (macOS 11+ / Big Sur+) + if [ "$MAC_MAJOR" -lt 11 ]; then + echo "[WARN] NomadArch requires macOS 11+ (Big Sur or later)" + echo "[INFO] Your version is $MAC_VERSION" + echo "[INFO] Please upgrade macOS to continue" + exit 1 + fi +else + echo "[WARN] Could not detect macOS version" + WARNINGS=$((WARNINGS + 1)) +fi + +# Check for Apple Silicon +ARCH=$(uname -m) +if [ "$ARCH" = "arm64" ]; then + echo "[OK] Apple Silicon detected (M1/M2/M3 chip)" +elif [ "$ARCH" = "x86_64" ]; then + echo "[OK] Intel Mac detected" +else + echo "[WARN] Unknown architecture: $ARCH" + WARNINGS=$((WARNINGS + 1)) +fi + +echo "" +echo "[STEP 2/7] Checking System Requirements..." +echo "" + +# Check for Node.js +echo "[INFO] Checking Node.js..." +if ! command -v node &> /dev/null; then + echo "[ERROR] Node.js not found!" + echo "" + echo "NomadArch requires Node.js to run." + echo "" + echo "Install Node.js using one of these methods:" + echo "" + echo " 1. Homebrew (recommended):" + echo " brew install node" + echo "" + echo " 2. Download from official site:" + echo " Visit https://nodejs.org/" + echo " Download and install the macOS installer" + echo "" + echo " 3. Using NVM (Node Version Manager):" + echo " curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash" + echo " source ~/.zshrc (or ~/.bash_profile)" + echo " nvm install 20" + echo "" + exit 1 +fi +NODE_VERSION=$(node --version) +echo "[OK] Node.js detected: $NODE_VERSION" + +# Check Node.js version (require 18+) +NODE_MAJOR=$(echo $NODE_VERSION | cut -d. -f1 | sed 's/v//') +if [ "$NODE_MAJOR" -lt 18 ]; then + echo "[WARN] Node.js version is too old (found v$NODE_VERSION, required 18+)" + echo "[INFO] Please update Node.js: brew upgrade node" + WARNINGS=$((WARNINGS + 1)) +fi + +# Check for npm +echo "[INFO] Checking npm..." +if ! command -v npm &> /dev/null; then + echo "[ERROR] npm not found! This should come with Node.js." + echo "Please reinstall Node.js" + ERRORS=$((ERRORS + 1)) +fi +NPM_VERSION=$(npm --version) +echo "[OK] npm detected: $NPM_VERSION" + +# Check for Xcode Command Line Tools (required for native modules) +echo "[INFO] Checking Xcode Command Line Tools..." +if ! command -v xcode-select &> /dev/null; then + echo "[WARN] Xcode Command Line Tools not installed" + echo "[INFO] Required for building native Node.js modules" + echo "" + echo "Install by running:" + echo " xcode-select --install" + echo "" + echo "This will open a dialog to install the tools." + WARNINGS=$((WARNINGS + 1)) +else + XCODE_PATH=$(xcode-select -p) + echo "[OK] Xcode Command Line Tools detected: $XCODE_PATH" +fi + +# Check for Homebrew (optional but recommended) +echo "[INFO] Checking Homebrew..." +if ! command -v brew &> /dev/null; then + echo "[WARN] Homebrew not found (optional but recommended)" + echo "[INFO] Install Homebrew from: https://brew.sh/" + echo "[INFO] Then you can install dependencies with: brew install node git" + WARNINGS=$((WARNINGS + 1)) +else + BREW_VERSION=$(brew --version | head -1) + echo "[OK] Homebrew detected: $BREW_VERSION" +fi + +# Check for Git (optional but recommended) +echo "[INFO] Checking Git..." +if ! command -v git &> /dev/null; then + echo "[WARN] Git not found (optional but recommended)" + echo "[INFO] Install: brew install git" + WARNINGS=$((WARNINGS + 1)) +else + GIT_VERSION=$(git --version) + echo "[OK] Git detected: $GIT_VERSION" +fi + +# Check disk space (at least 2GB free) +FREE_SPACE=$(df -BG "$PWD" | tail -1 | awk '{print int($4/1024/1024)}') +if [ "$FREE_SPACE" -lt 2048 ]; then + echo "[WARN] Low disk space ($FREE_SPACE MB free, recommended 2GB+)" + WARNINGS=$((WARNINGS + 1)) +else + echo "[OK] Disk space: $FREE_SPACE MB free" +fi + +echo "" +echo "[STEP 3/7] Checking Rosetta 2 (Apple Silicon)..." +echo "" + +# Check if Rosetta 2 is installed on Apple Silicon +if [ "$ARCH" = "arm64" ]; then + if ! /usr/bin/pgrep -q oahd; then + echo "[INFO] Rosetta 2 is not running" + echo "[INFO] Some x86_64 dependencies may need Rosetta" + echo "" + echo "Install Rosetta 2 if needed:" + echo " softwareupdate --install-rosetta" + echo "" + else + echo "[OK] Rosetta 2 is installed and running" + fi +fi + +echo "" +echo "[STEP 4/7] Checking Gatekeeper Status..." +echo "" + +# Check if Gatekeeper will block unsigned apps +echo "[INFO] Gatekeeper may block unsigned applications" +echo "[INFO] If NomadArch doesn't open, try:" +echo " Right-click -> Open" +echo " Or disable Gatekeeper (not recommended):" +echo " sudo spctl --master-disable" +echo "" + +echo "" +echo "[STEP 5/8] Downloading OpenCode Binary..." +echo "" + +if [ ! -d "bin" ]; then + mkdir bin +fi + +if [ ! -f "bin/opencode" ]; then + echo "[SETUP] Downloading opencode binary from GitHub releases..." + echo "[INFO] This is required for workspace functionality." + + # Detect architecture + if [ "$ARCH" = "arm64" ]; then + FILENAME="opencode-macos-arm64" + elif [ "$ARCH" = "x86_64" ]; then + FILENAME="opencode-macos" + else + echo "[WARN] Unsupported architecture: $ARCH" + echo "[INFO] Please download opencode manually from: https://opencode.ai/" + WARNINGS=$((WARNINGS + 1)) + FILENAME="" + fi + + if [ -n "$FILENAME" ]; then + curl -L -o "bin/opencode" "https://github.com/NeuralNomadsAI/NomadArch/releases/latest/download/$FILENAME" + if [ $? -ne 0 ]; then + echo "[WARN] Failed to download opencode automatically." + echo "[INFO] You can install OpenCode CLI manually from: https://opencode.ai/" + echo "[INFO] Or download opencode and place it in bin/ folder" + echo "[INFO] Without opencode, workspace creation will fail." + WARNINGS=$((WARNINGS + 1)) + else + chmod +x bin/opencode + echo "[OK] opencode downloaded successfully" + fi + fi +else + echo "[OK] opencode already exists" +fi + +echo "" +echo "[STEP 6/8] Setting Permissions..." +echo "" + +# Make scripts executable +chmod +x Launch-Unix.sh 2>/dev/null +chmod +x Install-Linux.sh 2>/dev/null +chmod +x Install-Mac.sh 2>/dev/null + +echo "[OK] Scripts permissions set" + +echo "" +echo "[STEP 7/8] Cleaning Previous Installation..." +echo "" + +if [ -d "node_modules" ]; then + echo "[INFO] Found existing node_modules, cleaning..." + rm -rf node_modules + echo "[OK] Cleaned previous installation artifacts" +else + echo "[OK] No previous installation found" +fi + +echo "" +echo "[STEP 8/8] Installing Dependencies..." +echo "" +echo "This may take 3-10 minutes depending on your internet speed." +echo "Please be patient and do not close this terminal." +echo "" + +npm install +if [ $? -ne 0 ]; then + echo "" + echo "[ERROR] npm install failed!" + echo "" + echo "Common solutions:" + echo " 1. Check your internet connection" + echo " 2. Try clearing npm cache: npm cache clean --force" + echo " 3. Delete node_modules and try again: rm -rf node_modules && npm install" + echo " 4. Ensure Xcode Command Line Tools are installed" + echo " 5. Check if Node.js version is 18+" + echo "" + echo "Attempting to clear npm cache and retry..." + npm cache clean --force + echo "Retrying installation..." + npm install + if [ $? -ne 0 ]; then + echo "[ERROR] Installation failed after retry." + ERRORS=$((ERRORS + 1)) + fi +else + echo "[OK] Dependencies installed successfully" +fi + +echo "" +echo "Building NomadArch..." +echo "" +echo "This may take 2-5 minutes depending on your system." +echo "" + +npm run build +if [ $? -ne 0 ]; then + echo "" + echo "[ERROR] Build failed!" + echo "" + echo "Common solutions:" + echo " 1. Check that Node.js version is 18+ (node --version)" + echo " 2. Ensure Xcode Command Line Tools are installed: xcode-select --install" + echo " 3. Clear npm cache: npm cache clean --force" + echo " 4. Delete node_modules and reinstall: rm -rf node_modules && npm install" + echo " 5. Check error messages above for specific issues" + echo "" + ERRORS=$((ERRORS + 1)) +else + echo "[OK] Build completed successfully" +fi + +echo "" +echo "Verifying Installation" +echo "" + +# Check if opencode binary exists +if [ ! -f "bin/opencode" ]; then + echo "[WARN] opencode binary not found. Workspace creation will fail." + echo "[INFO] Download from: https://github.com/NeuralNomadsAI/NomadArch/releases/latest/download/opencode-macos" + echo "[INFO] Or install OpenCode CLI from: https://opencode.ai/" + WARNINGS=$((WARNINGS + 1)) +else + echo "[OK] opencode binary verified" +fi + +echo "" +echo "Installation Summary" +echo "" + +if [ $ERRORS -gt 0 ]; then + echo "" + echo "════════════════════════════════════════════════════════════════════════════" + echo "[FAILED] Installation encountered $ERRORS error(s)!" + echo "" + echo "Please review error messages above and try again." + echo "For help, see: https://github.com/NeuralNomadsAI/NomadArch/issues" + echo "════════════════════════════════════════════════════════════════════════════" + echo "" + exit 1 +fi + +echo "" +echo "══════════════════════════════════════════════════════════════════════════" +echo "[SUCCESS] Installation Complete!" +echo "" + +if [ $WARNINGS -gt 0 ]; then + echo "[WARN] There were $WARNINGS warning(s) during installation." + echo "Review warnings above. Most warnings are non-critical." + echo "" +fi + +echo "You can now run NomadArch using:" +echo " ./Launch-Unix.sh" +echo "" +echo "For help and documentation, see: README.md" +echo "For troubleshooting, see: TROUBLESHOOTING.md" +echo "════════════════════════════════════════════════════════════════════════════" +echo "" +echo "Press Enter to start NomadArch now, or Ctrl+C to start later..." +read + +echo "" +echo "[INFO] Starting NomadArch..." +./Launch-Unix.sh diff --git a/.backup/Install-Windows.bat.backup b/.backup/Install-Windows.bat.backup new file mode 100644 index 0000000..d196020 --- /dev/null +++ b/.backup/Install-Windows.bat.backup @@ -0,0 +1,295 @@ +@echo off +title NomadArch Installer +color 0A +setlocal enabledelayedexpansion + +echo. +echo ███╗ ██╗ ██████╗ ███╗ ███╗ █████╗ ██████╗ █████╗ ██████╗ ██████╗██╗ ██╗ +echo ████╗ ██║██╔═══██╗████╗ ████║██╔══██╗██╔══██╗██╔══██╗██╔════╝██║ ██║ +echo ██╔██╗ ██║██║ ██║██╔████╔██║███████║██║ ██║███████║██████╔╝██║ ███████║ +echo ██║╚██╗██║██║ ██║██║╚██╔╝██║██╔══██║██║ ██║██╔══██║██╔══██╗██║ ██╔══██║ +echo ██║ ╚████║╚██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝██║ ██║██║ ██║╚██████╗██║ ██║ +echo ╚═╝ ╚═══╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ +echo. +echo INSTALLER - Enhanced with Auto-Dependency Resolution +echo ═══════════════════════════════════════════════════════════════════════════════ +echo. + +set ERRORS=0 +set WARNINGS=0 + +cd /d "%~dp0" + +echo [STEP 1/6] Checking System Requirements... +echo. + +:: Check for Administrator privileges +net session >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [WARN] Not running as Administrator. Some operations may fail. + set /a WARNINGS+=1 + echo. +) + +:: Check for Node.js +echo [INFO] Checking Node.js... +where node >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [ERROR] Node.js not found! + echo. + echo NomadArch requires Node.js to run. + echo. + echo Download from: https://nodejs.org/ + echo Recommended: Node.js 18.x LTS or 20.x LTS + echo. + echo Opening download page... + start "" "https://nodejs.org/" + echo. + echo Please install Node.js and run this installer again. + echo. + pause + exit /b 1 +) +for /f "tokens=*" %%i in ('node --version') do set NODE_VERSION=%%i +echo [OK] Node.js detected: %NODE_VERSION% + +:: Check Node.js version (require 18+) +for /f "tokens=1,2 delims=." %%a in ("%NODE_VERSION:v=%") do ( + set MAJOR=%%a + set MINOR=%%b +) +if %MAJOR% lss 18 ( + echo [WARN] Node.js version is too old (found v%MAJOR%.%MINOR%, required 18+) + echo [INFO] Please update Node.js from: https://nodejs.org/ + set /a WARNINGS+=1 +) + +:: Check for npm +echo [INFO] Checking npm... +where npm >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [ERROR] npm not found! This should come with Node.js. + echo Please reinstall Node.js from: https://nodejs.org/ + set /a ERRORS+=1 +) +for /f "tokens=*" %%i in ('npm --version') do set NPM_VERSION=%%i +echo [OK] npm detected: %NPM_VERSION% + +:: Check for Git (optional but recommended) +echo [INFO] Checking Git... +where git >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [WARN] Git not found (optional but recommended) + echo [INFO] Install from: https://git-scm.com/ + set /a WARNINGS+=1 +) else ( + for /f "tokens=*" %%i in ('git --version') do set GIT_VERSION=%%i + echo [OK] Git detected: %GIT_VERSION% +) + +:: Check for Python (optional, for some tools) +echo [INFO] Checking Python... +where python >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + where python3 >nul 2>&1 + if %ERRORLEVEL% neq 0 ( + echo [WARN] Python not found (optional, required for some build tools) + echo [INFO] Install from: https://www.python.org/downloads/ + set /a WARNINGS+=1 + ) else ( + echo [OK] Python3 detected + ) +) else ( + for /f "tokens=2" %%i in ('python --version') do set PY_VERSION=%%i + echo [OK] Python detected: %PY_VERSION% +) + +:: Check disk space (at least 2GB free) +for /f "tokens=3" %%a in ('dir /-c "%~dp0" ^| find "bytes free"') do set FREE_SPACE=%%a +set /a FREE_SPACE_GB=!FREE_SPACE!/1024/1024/1024 +if !FREE_SPACE_GB! lss 2 ( + echo [WARN] Low disk space (!FREE_SPACE_GB! GB free, recommended 2GB+) + set /a WARNINGS+=1 +) else ( + echo [OK] Disk space: !FREE_SPACE_GB! GB free +) + +echo. +echo [STEP 2/6] Cleaning Previous Installation... +echo. + +if exist "node_modules" ( + echo [INFO] Found existing node_modules, cleaning... + if exist "node_modules\.package-lock.json" ( + del /f /q "node_modules\.package-lock.json" 2>nul + ) + echo [OK] Cleaned previous installation artifacts +) else ( + echo [OK] No previous installation found +) + +echo. +echo [STEP 3/6] Downloading OpenCode Binary... +echo. + +if not exist "bin" mkdir bin +if not exist "bin\opencode.exe" ( + echo [SETUP] Downloading opencode.exe from GitHub releases... + echo [INFO] This is required for workspace functionality. + curl -L -o "bin\opencode.exe" "https://github.com/NeuralNomadsAI/NomadArch/releases/latest/download/opencode.exe" + if %ERRORLEVEL% neq 0 ( + echo [WARN] Failed to download opencode.exe automatically. + echo [INFO] You can install OpenCode CLI manually from: https://opencode.ai/ + echo [INFO] Or download opencode.exe and place it in bin/ folder + echo [INFO] Without opencode.exe, workspace creation will fail. + set /a WARNINGS+=1 + ) else ( + echo [OK] opencode.exe downloaded successfully + ) +) else ( + echo [OK] opencode.exe already exists +) + +echo. +echo [STEP 4/6] Installing Dependencies... +echo. +echo This may take 3-10 minutes depending on your internet speed. +echo Please be patient and do not close this window. +echo. + +call npm install +if %ERRORLEVEL% neq 0 ( + echo. + echo [ERROR] npm install failed! + echo. + echo Common solutions: + echo 1. Check your internet connection + echo 2. Try running as Administrator + echo 3. Clear npm cache: npm cache clean --force + echo 4. Delete node_modules and try again + echo. + echo Attempting to clear npm cache and retry... + call npm cache clean --force + echo Retrying installation... + call npm install + if %ERRORLEVEL% neq 0 ( + echo [ERROR] Installation failed after retry. + set /a ERRORS+=1 + ) +) else ( + echo [OK] Dependencies installed successfully +) + +echo. +echo [STEP 5/6] Building NomadArch... +echo. +echo This may take 2-5 minutes depending on your system. +echo. + +call npm run build +if %ERRORLEVEL% neq 0 ( + echo. + echo [ERROR] Build failed! + echo. + echo Common solutions: + echo 1. Check that Node.js version is 18+ (node --version) + echo 2. Clear npm cache: npm cache clean --force + echo 3. Delete node_modules and reinstall: rm -rf node_modules ^&^& npm install + echo 4. Check the error messages above for specific issues + echo. + set /a ERRORS+=1 +) else ( + echo [OK] Build completed successfully +) + +echo. +echo [STEP 6/6] Verifying Installation... +echo. + +:: Check UI build +if not exist "packages\ui\dist" ( + echo [WARN] UI build not found + set /a WARNINGS+=1 +) else ( + echo [OK] UI build verified +) + +:: Check Server build +if not exist "packages\server\dist\bin.js" ( + echo [WARN] Server build not found + set /a WARNINGS+=1 +) else ( + echo [OK] Server build verified +) + +:: Check Electron build +if not exist "packages\electron-app\dist\main\main.js" ( + echo [WARN] Electron build not found + set /a WARNINGS+=1 +) else ( + echo [OK] Electron build verified +) + +:: Check opencode.exe +if not exist "bin\opencode.exe" ( + echo [WARN] opencode.exe not found. Workspace creation will fail. + echo [INFO] Download from: https://github.com/NeuralNomadsAI/NomadArch/releases/latest/download/opencode.exe + echo [INFO] Or install OpenCode CLI from: https://opencode.ai/ + set /a WARNINGS+=1 +) else ( + echo [OK] opencode.exe verified +) + +echo. +echo [STEP 7/7] Installation Summary +echo. + +if %ERRORS% gtr 0 ( + echo. + echo ═══════════════════════════════════════════════════════════════════════════════ + echo [FAILED] Installation encountered %ERRORS% error^(s^)! + echo. + echo Please review the error messages above and try again. + echo For help, see: https://github.com/NeuralNomadsAI/NomadArch/issues + echo ═══════════════════════════════════════════════════════════════════════════════ + echo. + pause + exit /b 1 +) + +echo. +echo ═══════════════════════════════════════════════════════════════════════════════ +echo [SUCCESS] Installation Complete! +echo. +if %WARNINGS% gtr 0 ( + echo [WARN] There were %WARNINGS% warning^(s^) during installation. + echo Review the warnings above. Most warnings are non-critical. + echo. +) +echo You can now run NomadArch using: +echo - Launch-Windows.bat ^(Production mode^) +echo - Launch-Dev-Windows.bat ^(Developer mode with hot reload^) +echo - NomadArch.vbs ^(Silent mode, no console window^) +echo. +echo For help and documentation, see: README.md +echo For troubleshooting, see: TROUBLESHOOTING.md +echo ═══════════════════════════════════════════════════════════════════════════════ +echo. +echo Press any key to start NomadArch now, or close this window to start later... +pause >nul + +:: Offer to start the app +echo. +echo [OPTION] Would you like to start NomadArch now? ^(Y/N^) +set /p START_APP="> " +if /i "%START_APP%"=="Y" ( + echo. + echo [INFO] Starting NomadArch... + call Launch-Windows.bat +) else ( + echo. + echo [INFO] You can start NomadArch later by running Launch-Windows.bat + echo. +) + +exit /b 0 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..2898d23 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,71 @@ +name: Bug Report +description: Report a bug or regression in CodeNomad +labels: + - bug +title: "[Bug]: " +body: + - type: markdown + attributes: + value: | + Thanks for filing a bug report! Please review open issues before submitting a new one and provide as much detail as possible so we can reproduce the problem. + - type: dropdown + id: variant + attributes: + label: App Variant + description: Which build are you running when this issue appears? + multiple: false + options: + - Electron + - Tauri + - Server CLI + validations: + required: true + - type: input + id: os-version + attributes: + label: Operating System & Version + description: Include the OS family and version (e.g., macOS 15.0, Ubuntu 24.04, Windows 11 23H2). + placeholder: macOS 15.0 + validations: + required: true + - type: input + id: summary + attributes: + label: Issue Summary + description: Briefly describe what is happening. + placeholder: A quick one sentence problem statement + validations: + required: true + - type: textarea + id: repro + attributes: + label: Steps to Reproduce + description: List the steps needed to reproduce the problem. + placeholder: | + 1. Go to ... + 2. Click ... + 3. Observe ... + validations: + required: true + - type: textarea + id: expected + attributes: + label: Expected Behavior + description: Describe what you expected to happen instead. + validations: + required: true + - type: textarea + id: logs + attributes: + label: Logs & Screenshots + description: Attach relevant logs, stack traces, or screenshots if available. + placeholder: Paste logs here or drag-and-drop files onto the issue. + validations: + required: false + - type: textarea + id: extra + attributes: + label: Additional Context + description: Add any other context about the problem here. + validations: + required: false diff --git a/.github/workflows/build-and-upload.yml b/.github/workflows/build-and-upload.yml new file mode 100644 index 0000000..0f01652 --- /dev/null +++ b/.github/workflows/build-and-upload.yml @@ -0,0 +1,519 @@ +name: Build and Upload Binaries + +on: + workflow_call: + inputs: + version: + description: "Version to apply to workspace packages" + required: true + type: string + tag: + description: "Git tag to upload assets to" + required: true + type: string + release_name: + description: "Release name (unused here, for context)" + required: true + type: string + +permissions: + id-token: write + contents: write + +env: + NODE_VERSION: 20 + +jobs: + build-macos: + runs-on: macos-15-intel + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Set workspace versions + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-darwin-x64 --no-save + + - name: Build macOS binaries (Electron) + run: npm run build:mac --workspace @neuralnomads/codenomad-electron-app + + - name: Upload release assets + run: | + set -euo pipefail + shopt -s nullglob + for file in packages/electron-app/release/*.zip; do + [ -f "$file" ] || continue + echo "Uploading $file" + gh release upload "$TAG" "$file" --clobber + done + + build-windows: + runs-on: windows-2025 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Set workspace versions + run: npm version ${{ env.VERSION }} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + shell: bash + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-win32-x64-msvc --no-save + + - name: Build Windows binaries (Electron) + run: npm run build:win --workspace @neuralnomads/codenomad-electron-app + + - name: Upload release assets + shell: pwsh + run: | + Get-ChildItem -Path "packages/electron-app/release" -Filter *.zip -File | ForEach-Object { + Write-Host "Uploading $($_.FullName)" + gh release upload $env:TAG $_.FullName --clobber + } + + build-linux: + runs-on: ubuntu-24.04 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Set workspace versions + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-linux-x64-gnu --no-save + + - name: Build Linux binaries (Electron) + run: npm run build:linux --workspace @neuralnomads/codenomad-electron-app + + - name: Upload release assets + run: | + set -euo pipefail + shopt -s nullglob + for file in packages/electron-app/release/*.zip; do + [ -f "$file" ] || continue + echo "Uploading $file" + gh release upload "$TAG" "$file" --clobber + done + + build-tauri-macos: + runs-on: macos-15-intel + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Setup Rust (Tauri) + uses: dtolnay/rust-toolchain@stable + + - name: Set workspace versions + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-darwin-x64 --no-save + + - name: Build macOS bundle (Tauri) + run: npm run build --workspace @codenomad/tauri-app + + - name: Package Tauri artifacts (macOS) + run: | + set -euo pipefail + BUNDLE_ROOT="packages/tauri-app/target/release/bundle" + ARTIFACT_DIR="packages/tauri-app/release-tauri" + rm -rf "$ARTIFACT_DIR" + mkdir -p "$ARTIFACT_DIR" + if [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then + ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-x64.zip" + fi + + - name: Upload Tauri release assets (macOS) + run: | + set -euo pipefail + shopt -s nullglob + for file in packages/tauri-app/release-tauri/*.zip; do + [ -f "$file" ] || continue + echo "Uploading $file" + gh release upload "$TAG" "$file" --clobber + done + + build-tauri-macos-arm64: + runs-on: macos-26 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Setup Rust (Tauri) + uses: dtolnay/rust-toolchain@stable + + - name: Set workspace versions + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-darwin-arm64 --no-save + + - name: Build macOS bundle (Tauri, arm64) + run: npm run build --workspace @codenomad/tauri-app + + - name: Package Tauri artifacts (macOS arm64) + run: | + set -euo pipefail + BUNDLE_ROOT="packages/tauri-app/target/release/bundle" + ARTIFACT_DIR="packages/tauri-app/release-tauri" + rm -rf "$ARTIFACT_DIR" + mkdir -p "$ARTIFACT_DIR" + if [ -d "$BUNDLE_ROOT/macos/CodeNomad.app" ]; then + ditto -ck --sequesterRsrc --keepParent "$BUNDLE_ROOT/macos/CodeNomad.app" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-macos-arm64.zip" + fi + + - name: Upload Tauri release assets (macOS arm64) + run: | + set -euo pipefail + shopt -s nullglob + for file in packages/tauri-app/release-tauri/*.zip; do + [ -f "$file" ] || continue + echo "Uploading $file" + gh release upload "$TAG" "$file" --clobber + done + + build-tauri-windows: + runs-on: windows-2025 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Setup Rust (Tauri) + uses: dtolnay/rust-toolchain@stable + + - name: Set workspace versions + run: npm version ${{ env.VERSION }} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + shell: bash + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-win32-x64-msvc --no-save + + - name: Build Windows bundle (Tauri) + run: npm run build --workspace @codenomad/tauri-app + + - name: Package Tauri artifacts (Windows) + shell: pwsh + run: | + $bundleRoot = "packages/tauri-app/target/release/bundle" + $artifactDir = "packages/tauri-app/release-tauri" + if (Test-Path $artifactDir) { Remove-Item $artifactDir -Recurse -Force } + New-Item -ItemType Directory -Path $artifactDir | Out-Null + $exe = Get-ChildItem -Path $bundleRoot -Recurse -File -Filter *.exe | Select-Object -First 1 + if ($null -ne $exe) { + $dest = Join-Path $artifactDir ("CodeNomad-Tauri-$env:VERSION-windows-x64.zip") + Compress-Archive -Path $exe.Directory.FullName -DestinationPath $dest -Force + } + + - name: Upload Tauri release assets (Windows) + shell: pwsh + run: | + if (Test-Path "packages/tauri-app/release-tauri") { + Get-ChildItem -Path "packages/tauri-app/release-tauri" -Filter *.zip -File | ForEach-Object { + Write-Host "Uploading $($_.FullName)" + gh release upload $env:TAG $_.FullName --clobber + } + } + + build-tauri-linux: + runs-on: ubuntu-24.04 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Setup Rust (Tauri) + uses: dtolnay/rust-toolchain@stable + + - name: Install Linux build dependencies (Tauri) + run: | + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + pkg-config \ + libgtk-3-dev \ + libglib2.0-dev \ + libwebkit2gtk-4.1-dev \ + libsoup-3.0-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev + + - name: Set workspace versions + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-linux-x64-gnu --no-save + + - name: Build Linux bundle (Tauri) + run: npm run build --workspace @codenomad/tauri-app + + - name: Package Tauri artifacts (Linux) + run: | + set -euo pipefail + SEARCH_ROOT="packages/tauri-app/target" + ARTIFACT_DIR="packages/tauri-app/release-tauri" + rm -rf "$ARTIFACT_DIR" + mkdir -p "$ARTIFACT_DIR" + shopt -s nullglob globstar + + find_one() { + find "$SEARCH_ROOT" -type f -iname "$1" | head -n1 + } + + appimage=$(find_one "*.AppImage") + deb=$(find_one "*.deb") + rpm=$(find_one "*.rpm") + + if [ -z "$appimage" ] || [ -z "$deb" ] || [ -z "$rpm" ]; then + echo "Missing bundle(s): appimage=${appimage:-none} deb=${deb:-none} rpm=${rpm:-none}" >&2 + exit 1 + fi + + cp "$appimage" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.AppImage" + cp "$deb" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.deb" + cp "$rpm" "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.rpm" + + - name: Upload Tauri release assets (Linux) + run: | + set -euo pipefail + shopt -s nullglob + for file in packages/tauri-app/release-tauri/*; do + [ -f "$file" ] || continue + echo "Uploading $file" + gh release upload "$TAG" "$file" --clobber + done + + build-tauri-linux-arm64: + if: ${{ false }} + runs-on: ubuntu-24.04 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/arm64 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Setup Rust (Tauri) + uses: dtolnay/rust-toolchain@stable + with: + targets: aarch64-unknown-linux-gnu + + - name: Install Linux build dependencies (Tauri) + run: | + sudo dpkg --add-architecture arm64 + sudo tee /etc/apt/sources.list.d/arm64.list >/dev/null <<'EOF' + deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse + deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse + deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse + deb [arch=arm64] http://ports.ubuntu.com/ubuntu-ports noble-backports main restricted universe multiverse + EOF + sudo apt-get update + sudo apt-get install -y \ + build-essential \ + pkg-config \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu \ + libgtk-3-dev:arm64 \ + libglib2.0-dev:arm64 \ + libwebkit2gtk-4.1-dev:arm64 \ + libsoup-3.0-dev:arm64 \ + libayatana-appindicator3-dev:arm64 \ + librsvg2-dev:arm64 + + - name: Set workspace versions + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-linux-arm64-gnu --no-save + + - name: Build Linux bundle (Tauri arm64) + env: + TAURI_BUILD_TARGET: aarch64-unknown-linux-gnu + PKG_CONFIG_PATH: /usr/lib/aarch64-linux-gnu/pkgconfig + CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc + CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ + AR_aarch64_unknown_linux_gnu: aarch64-linux-gnu-ar + run: npm run build --workspace @codenomad/tauri-app + + - name: Package Tauri artifacts (Linux arm64) + run: | + set -euo pipefail + SEARCH_ROOT="packages/tauri-app/target" + ARTIFACT_DIR="packages/tauri-app/release-tauri" + rm -rf "$ARTIFACT_DIR" + mkdir -p "$ARTIFACT_DIR" + shopt -s nullglob globstar + first_artifact=$(find "$SEARCH_ROOT" -type f \( -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" -o -name "*.tar.gz" \) | head -n1) + fallback_bin="$SEARCH_ROOT/release/codenomad-tauri" + if [ -n "$first_artifact" ]; then + zip -j "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.zip" "$first_artifact" + elif [ -f "$fallback_bin" ]; then + zip -j "$ARTIFACT_DIR/CodeNomad-Tauri-${VERSION}-linux-x64.zip" "$fallback_bin" + else + echo "No bundled artifact found under $SEARCH_ROOT and no binary at $fallback_bin" >&2 + exit 1 + fi + + + - name: Upload Tauri release assets (Linux arm64) + run: | + set -euo pipefail + shopt -s nullglob + for file in packages/tauri-app/release-tauri/*.zip; do + [ -f "$file" ] || continue + echo "Uploading $file" + gh release upload "$TAG" "$file" --clobber + done + + + build-linux-rpm: + runs-on: ubuntu-24.04 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ inputs.version }} + TAG: ${{ inputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + + - name: Install rpm packaging dependencies + run: | + sudo apt-get update + sudo apt-get install -y rpm ruby ruby-dev build-essential + sudo gem install --no-document fpm + + - name: Set workspace versions + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Install project dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-linux-x64-gnu --no-save + + - name: Build Linux RPM binaries + run: npm run build:linux-rpm --workspace @neuralnomads/codenomad-electron-app + + - name: Upload RPM release assets + run: | + set -euo pipefail + shopt -s nullglob + for file in packages/electron-app/release/*.rpm; do + [ -f "$file" ] || continue + echo "Uploading $file" + gh release upload "$TAG" "$file" --clobber + done diff --git a/.github/workflows/dev-release.yml b/.github/workflows/dev-release.yml new file mode 100644 index 0000000..aa24529 --- /dev/null +++ b/.github/workflows/dev-release.yml @@ -0,0 +1,16 @@ +name: Dev Release + +on: + workflow_dispatch: + +permissions: + id-token: write + contents: write + +jobs: + dev-release: + uses: ./.github/workflows/reusable-release.yml + with: + version_suffix: -dev + dist_tag: dev + secrets: inherit diff --git a/.github/workflows/manual-npm-publish.yml b/.github/workflows/manual-npm-publish.yml new file mode 100644 index 0000000..86b8768 --- /dev/null +++ b/.github/workflows/manual-npm-publish.yml @@ -0,0 +1,74 @@ +name: Manual NPM Publish + +on: + workflow_dispatch: + inputs: + version: + description: "Version to publish (e.g. 0.2.0-dev)" + required: false + type: string + dist_tag: + description: "npm dist-tag" + required: false + default: dev + type: string + workflow_call: + inputs: + version: + required: true + type: string + dist_tag: + required: false + type: string + default: dev + +permissions: + contents: read + id-token: write + +jobs: + publish: + runs-on: ubuntu-latest + env: + NODE_VERSION: 20 + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + registry-url: https://registry.npmjs.org + + - name: Ensure npm >=11.5.1 + run: npm install -g npm@latest + + - name: Install dependencies + run: npm ci --workspaces + + - name: Ensure rollup native binary + run: npm install @rollup/rollup-linux-x64-gnu --no-save + + - name: Build server package (includes UI bundling) + run: npm run build --workspace @neuralnomads/codenomad + + - name: Set publish metadata + shell: bash + run: | + VERSION_INPUT="${{ inputs.version }}" + if [ -z "$VERSION_INPUT" ]; then + VERSION_INPUT=$(node -p "require('./package.json').version") + fi + echo "VERSION=$VERSION_INPUT" >> "$GITHUB_ENV" + echo "DIST_TAG=${{ inputs.dist_tag || 'dev' }}" >> "$GITHUB_ENV" + + - name: Bump package version for publish + run: npm version ${VERSION} --workspaces --include-workspace-root --no-git-tag-version --allow-same-version + + - name: Publish server package with provenance + env: + NPM_CONFIG_PROVENANCE: true + NPM_CONFIG_REGISTRY: https://registry.npmjs.org + run: | + npm publish --workspace @neuralnomads/codenomad --access public --tag ${DIST_TAG} --provenance diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..dfd07e8 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,17 @@ +name: Release Binaries + +on: + push: + branches: + - main + +permissions: + id-token: write + contents: write + +jobs: + release: + uses: ./.github/workflows/reusable-release.yml + with: + dist_tag: latest + secrets: inherit diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml new file mode 100644 index 0000000..026fd8f --- /dev/null +++ b/.github/workflows/reusable-release.yml @@ -0,0 +1,80 @@ +name: Reusable Release + +on: + workflow_call: + inputs: + version_suffix: + description: "Suffix appended to package.json version" + required: false + default: "" + type: string + dist_tag: + description: "npm dist-tag to publish under" + required: false + default: dev + type: string + +permissions: + id-token: write + contents: write + +env: + NODE_VERSION: 20 + +jobs: + prepare-release: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.versions.outputs.version }} + tag: ${{ steps.versions.outputs.tag }} + release_name: ${{ steps.versions.outputs.release_name }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + + - name: Compute release versions + id: versions + env: + VERSION_SUFFIX: ${{ inputs.version_suffix }} + run: | + BASE_VERSION=$(node -p "require('./package.json').version") + VERSION="${BASE_VERSION}${VERSION_SUFFIX}" + TAG="v${VERSION}" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "release_name=$TAG" >> "$GITHUB_OUTPUT" + + - name: Create GitHub release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAG: ${{ steps.versions.outputs.tag }} + run: | + if gh release view "$TAG" >/dev/null 2>&1; then + echo "Release $TAG already exists" + else + gh release create "$TAG" --title "$TAG" --generate-notes + fi + + build-and-upload: + needs: prepare-release + uses: ./.github/workflows/build-and-upload.yml + with: + version: ${{ needs.prepare-release.outputs.version }} + tag: ${{ needs.prepare-release.outputs.tag }} + release_name: ${{ needs.prepare-release.outputs.release_name }} + secrets: inherit + + publish-server: + needs: + - prepare-release + - build-and-upload + uses: ./.github/workflows/manual-npm-publish.yml + with: + version: ${{ needs.prepare-release.outputs.version }} + dist_tag: ${{ inputs.dist_tag }} + secrets: inherit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5ab428 --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +# ===================================================== +# NomadArch - Git Ignore Configuration +# Clean public repository version +# ===================================================== + +# ===================== Dependencies ===================== +node_modules/ +.pnpm-store/ +.yarn/ + +# ===================== Build Outputs ==================== +dist/ +release/ +out/ +*.bundle.js +*.bundle.js.map + +# ===================== IDE & Editor ===================== +.DS_Store +.idea/ +*.swp +*.swo +.vscode/ +*.code-workspace +.dir-locals.el + +# ===================== Vite / Build Tools =============== +.vite/ +.electron-vite/ +*.local + +# ===================== Logs & Debug ===================== +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +install.log +.tmp-*.log + +# ===================== OS Generated Files =============== +Thumbs.db +ehthumbs.db +Desktop.ini + +# ===================== Temporary Files ================== +*.tmp +*.temp +.tmp-*/ +.cache/ +*.bak + +# ===================== Environment & Secrets ============ +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +.env*.local +*.pem +*.key +secrets/ +credentials/ + +# ===================== OpenCode Data ==================== +.opencode/ +!.opencode/.gitignore + +# ===================== Session & User Data ============== +.trae/ +.agent/artifacts/ +.backup/ +.tmp-qwen-code/ + +# ===================== MCP Config (may contain keys) === +# Keep the template but user should configure their own +# .mcp.json + +# ===================== Test Coverage ==================== +coverage/ +.nyc_output/ + +# ===================== Electron Build =================== +packages/electron-app/dist/ +packages/electron-app/out/ +packages/electron-app/release/ + +# ===================== UI Build ========================= +packages/ui/dist/ +packages/ui/renderer/dist/ + +# ===================== Server Build ===================== +packages/server/dist/ + +# ===================== Lock files (optional) ============ +# package-lock.json +# pnpm-lock.yaml +# yarn.lock + +# ===================== Backup Files ===================== +*.backup +*_backup* +_backup_original/ \ No newline at end of file diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..49e9a3a --- /dev/null +++ b/.mcp.json @@ -0,0 +1,32 @@ +{ + "mcpServers": { + "sequential-thinking": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-sequential-thinking" + ] + }, + "desktop-commander": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-desktop-commander" + ] + }, + "web-reader": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-web-reader" + ] + }, + "github": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-github" + ] + } + } +} \ No newline at end of file diff --git a/.opencode/agent/web_developer.md b/.opencode/agent/web_developer.md new file mode 100644 index 0000000..97e7f80 --- /dev/null +++ b/.opencode/agent/web_developer.md @@ -0,0 +1,5 @@ +--- +description: Develops Web UI components. +mode: all +--- +You are a Web Frontend Developer Agent. Your primary focus is on developing SolidJS UI components, ensuring adherence to modern web best practices, excellent UI/UX, and efficient data integration. diff --git a/.tmp-qwen-code b/.tmp-qwen-code new file mode 160000 index 0000000..a92be72 --- /dev/null +++ b/.tmp-qwen-code @@ -0,0 +1 @@ +Subproject commit a92be72e88b4ad31004a8a2177e7ee046378d992 diff --git a/.trae/documents/Implement AI-Powered Smart Session Compression.md b/.trae/documents/Implement AI-Powered Smart Session Compression.md new file mode 100644 index 0000000..cda2eb8 --- /dev/null +++ b/.trae/documents/Implement AI-Powered Smart Session Compression.md @@ -0,0 +1,140 @@ +## Implementation Plan: Enhanced Session Compaction System (9 High-Priority Fixes) + +### Phase 1: Core Foundation (Types & Configuration) + +**NEW: `packages/ui/src/stores/session-compaction.ts`** + +1. **Compaction Types & Interfaces** + - `CompactionMessageFlags`: summary, mode, provenance flags + - `StructuredSummary`: Tier A/B schema with what_was_done, files, current_state, key_decisions, next_steps, blockers, artifacts, tags, provenance + - `CompactionEvent`: Audit trail with event_id, timestamp, actor, trigger_reason, token_before/after, model_used, cost_estimate + - `CompactionConfig`: autoCompactEnabled, autoCompactThreshold, compactPreserveWindow, pruneReclaimThreshold, userPreference, undoRetentionWindow + - `SessionCompactingHook`: Plugin contract for domain-specific rules + +2. **Configuration Store** + - Default config: auto=80%, preserve=40k tokens, prune_threshold=20k, preference="ask" + - Export functions: `getCompactionConfig()`, `updateCompactionConfig()` + +### Phase 2: Overflow Detection Engine + +**MODIFY: `packages/ui/src/stores/session-compaction.ts`** + +3. **Token Monitoring Functions** + - `isOverflowDetected(usage, modelLimit)`: Check if usage >= threshold% + - `shouldPruneToolOutputs(usage)`: Check if tool outputs > reclaim threshold + - `estimateTokenReduction(before, after)`: Calculate % reduction + +4. **Audit Trail System** + - `recordCompactionEvent(sessionId, event)`: Append-only to audit log + - `getCompactionHistory(sessionId)`: Retrieve audit trail + - `exportAuditLog()`: For compliance/debugging + +### Phase 3: Secrets Detection & Sanitization + +**NEW: `packages/ui/src/lib/secrets-detector.ts`** + +5. **Secrets Detector** + - Pattern matching for: api keys, passwords, tokens, secrets, credentials + - `redactSecrets(content)`: Returns { clean: string, redactions: { path, reason }[] } + - Placeholder format: `[REDACTED: {reason}]` + +### Phase 4: AI-Powered Compaction Agent + +**MODIFY: `packages/ui/src/stores/session-compaction.ts`** + +6. **Compaction Agent Integration** + - `COMPACTION_AGENT_PROMPT`: Structured prompt with instructions + - `generateCompactionSummary(instanceId, sessionId, window)`: Call sendMessage() to get AI summary + - Parse response into Tier A (human) and Tier B (structured JSON) + +7. **Execute Compaction** + - `executeCompaction(instanceId, sessionId, mode)`: Main compaction orchestration + - Steps: enumerate → plugin hooks → AI summary → sanitize → store → prune → audit + - Returns: preview, token estimate, compaction event + +### Phase 5: Pruning Engine + +**MODIFY: `packages/ui/src/stores/session-compaction.ts`** + +8. **Sliding Window Pruning** + - `pruneToolOutputs(instanceId, sessionId)`: Maintain queue, prune oldest > threshold + - `isToolOutput(part)`: Classify build logs, test logs, large JSON + +### Phase 6: Undo & Rehydration + +**MODIFY: `packages/ui/src/stores/session-compaction.ts`** + +9. **Undo System** + - `undoCompaction(sessionId, compactionEventId)`: Rehydrate within retention window + - `getCompactedSessionSummary(sessionId)`: Retrieve stored summary + - `expandCompactedView(sessionId)`: Return archived messages + +### Phase 7: Integration + +**MODIFY: `packages/ui/src/stores/session-events.ts`** + +10. **Auto-Compact Trigger** + - Monitor `EventSessionUpdated` for token usage + - Trigger based on user preference (auto/ask/never) + - Call existing `showConfirmDialog()` with compaction preview + +**MODIFY: `packages/ui/src/stores/session-actions.ts`** + +11. **Replace compactSession** + - Use new `executeCompaction()` function + - Support both "prune" and "compact" modes + +### Phase 8: Schema Validation + +**NEW: `packages/ui/src/lib/compaction-validation.ts`** + +12. **Schema Validation** + - `validateStructuredSummary(summary)`: Zod schema for Tier B + - `validateCompactionEvent(event)`: Zod schema for audit trail + - `ValidationErrors` type with path, message, code + +### Phase 9: CI Tests + +**NEW: `packages/ui/src/stores/session-compaction.test.ts`** + +13. **Test Coverage** + - `test_overflow_detection`: Verify threshold calculation + - `test_secrets_redaction`: Verify patterns are caught + - `test_compaction_execution`: Full compaction flow + - `test_undo_rehydration`: Verify restore works + - `test_plugin_hooks`: Verify custom rules apply + +### Phase 10: Canary Rollout + +**MODIFY: `packages/ui/src/stores/session-compaction.ts`** + +14. **Feature Flag** + - `ENABLE_SMART_COMPACTION`: Environment variable or config flag + - Default: `false` for canary, set to `true` for full rollout + - Graceful degradation: fall back to simple compaction if disabled + +--- + +## Implementation Order (Priority) + +1. **P0 - Foundation**: Types, config, schema validation (1-2, 12) +2. **P0 - Core Engine**: Overflow detection, secrets detector (3-5) +3. **P0 - AI Integration**: Compaction agent, execute function (6-7) +4. **P1 - Pruning**: Tool output classification, sliding window (8) +5. **P1 - Undo**: Rehydration system (9) +6. **P1 - Integration**: Session events, actions integration (10-11) +7. **P2 - Tests**: CI test coverage (13) +8. **P2 - Rollout**: Feature flag, canary enablement (14) + +--- + +## Success Criteria + +- ✅ AI generates meaningful summaries (not just "0 AI responses") +- ✅ Overflow detected before context limit exceeded +- ✅ Secrets are redacted before storage +- ✅ Audit trail tracks every compaction +- ✅ Undo works within retention window +- ✅ Schema validation prevents corrupt data +- ✅ CI tests ensure reliability +- ✅ Canary flag allows safe rollout \ No newline at end of file diff --git a/.trae/documents/STRICT FIX ORDER_ Installers_Launchers Audit (8 Fixes).md b/.trae/documents/STRICT FIX ORDER_ Installers_Launchers Audit (8 Fixes).md new file mode 100644 index 0000000..ea9f97e --- /dev/null +++ b/.trae/documents/STRICT FIX ORDER_ Installers_Launchers Audit (8 Fixes).md @@ -0,0 +1,391 @@ +# FINAL EXECUTION PLAN - 8 Fixes with Proof Deliverables + +## Fix Summary + +| Fix | Files | Deliverables | +|------|--------|-------------| +| C1 | Install-Windows.bat, Install-Mac.sh, Install-Linux.sh, Launch-Windows.bat, Launch-Dev-Windows.bat, Launch-Unix.sh | 9 path diffs + `dir packages\ui\dist` verification | +| C2 | packages/ui/vite.config.ts, Launch-Dev-Windows.bat, Launch-Dev-Unix.sh (NEW) | vite.config.ts diff + 2 launcher diffs + Vite log showing port | +| C3 | Launch-Windows.bat, Launch-Dev-Windows.bat, Launch-Unix.sh | 3 CLI_PORT env var diffs + server log showing port | +| C4 | Install-Windows.bat, Install-Mac.sh, Install-Linux.sh | 3 download/checksum diffs + log verification | +| C5 | Install-Windows.bat | Certutil parsing diff + hash output | +| C6 | Install-Windows.bat, Install-Mac.sh, Install-Linux.sh | 3 TARGET_DIR/BIN_DIR diffs + fallback test output | +| C7 | Install-Windows.bat, Install-Mac.sh, Install-Linux.sh | 3 health check path diffs + health check output | +| C8 | Launch-Dev-Windows.bat | 1 path diff + grep verification | + +--- + +## C1: UI Build Path Correction + +**Files:** Install-Windows.bat (lines 194, 245), Install-Mac.sh (204, 256), Install-Linux.sh (220, 272), Launch-Windows.bat (185), Launch-Dev-Windows.bat (144), Launch-Unix.sh (178) + +**Diff:** +```batch +# All Windows scripts - replace: +packages\ui\src\renderer\dist +# With: +packages\ui\dist + +# All Unix scripts - replace: +packages/ui/src/renderer/dist +# With: +packages/ui/dist +``` + +**Verification:** `dir packages\ui\dist` + `dir packages\ui\dist\index.html` + +--- + +## C2: Vite Dev Server Port Wiring + +**File 1: packages/ui/vite.config.ts (line 23)** + +```diff +- server: { +- port: 3000, +- }, ++ server: { ++ port: Number(process.env.VITE_PORT ?? 3000), ++ }, +``` + +**File 2: Launch-Dev-Windows.bat (after port detection)** + +```diff +- start "NomadArch UI" cmd /k "cd /d \"%~dp0packages\ui\" && set VITE_PORT=!UI_PORT! && npm run dev" ++ start "NomadArch UI" cmd /k "cd /d \"%~dp0packages\ui\" && set VITE_PORT=!UI_PORT! && npm run dev -- --port !UI_PORT!" +``` + +**File 3: Launch-Dev-Unix.sh (NEW FILE)** + +```bash +#!/bin/bash +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Port detection +DEFAULT_SERVER_PORT=3001 +DEFAULT_UI_PORT=5173 +SERVER_PORT=$DEFAULT_SERVER_PORT +UI_PORT=$DEFAULT_UI_PORT + +echo "[INFO] Detecting available ports..." + +# Server port (3001-3050) +for port in {3001..3050}; do + if ! lsof -i :$port -sTCP:LISTEN -t > /dev/null 2>&1; then + SERVER_PORT=$port + break + fi +done + +# UI port (5173-5200) +for port in {5173..5200}; do + if ! lsof -i :$port -sTCP:LISTEN -t > /dev/null 2>&1; then + UI_PORT=$port + break + fi +done + +echo "[INFO] Using server port: $SERVER_PORT" +echo "[INFO] Using UI port: $UI_PORT" + +# Start server with CLI_PORT +echo "[INFO] Starting Backend Server..." +cd packages/server +export CLI_PORT=$SERVER_PORT +npm run dev & +SERVER_PID=$! + +sleep 3 + +# Start UI with VITE_PORT + --port flag +echo "[INFO] Starting Frontend UI..." +cd "$SCRIPT_DIR/packages/ui" +export VITE_PORT=$UI_PORT +npm run dev -- --port $UI_PORT & +UI_PID=$! + +sleep 3 + +# Start Electron +echo "[INFO] Starting Electron..." +cd "$SCRIPT_DIR/packages/electron-app" +npm run dev + +# Cleanup on exit +trap "kill $SERVER_PID $UI_PID 2>/dev/null; exit" INT TERM +``` + +**Verification:** Vite log output showing `Local: http://localhost:` + +--- + +## C3: Server Port Environment Variable + +**Launch-Windows.bat (before npm run dev:electron):** + +```diff +echo [INFO] Starting NomadArch... +set SERVER_URL=http://localhost:!SERVER_PORT! +echo [INFO] Server will run on http://localhost:!SERVER_PORT! ++ ++ set CLI_PORT=!SERVER_PORT! +call npm run dev:electron +``` + +**Launch-Dev-Windows.bat (server start command):** + +```diff +echo [INFO] Starting Backend Server... +- start "NomadArch Server" cmd /k "cd /d \"%~dp0packages\server\" && npm run dev" ++ start "NomadArch Server" cmd /k "cd /d \"%~dp0packages\server\" && set CLI_PORT=!SERVER_PORT! && npm run dev" +``` + +**Launch-Unix.sh (before npm run dev:electron):** + +```bash +echo -e "${GREEN}[INFO]${NC} Starting NomadArch..." +SERVER_URL="http://localhost:$SERVER_PORT" +echo -e "${GREEN}[INFO]${NC} Server will run on http://localhost:$SERVER_PORT" + +export CLI_PORT=$SERVER_PORT +npm run dev:electron +``` + +**Verification:** Server log showing `CodeNomad Server is ready at http://127.0.0.1:` + +--- + +## C4: OpenCode Download with Dynamic Version + Checksum + +**Install-Windows.bat (lines 165-195):** + +```batch +set TARGET_DIR=%SCRIPT_DIR% +set BIN_DIR=%TARGET_DIR%\bin +if not exist "%BIN_DIR%" mkdir "%BIN_DIR%" + +:: Resolve latest version from GitHub API +echo [INFO] Resolving latest OpenCode version... +for /f "delims=" %%v in ('curl -s https://api.github.com/repos/sst/opencode/releases/latest ^| findstr "\"tag_name\""') do ( + set OPENCODE_VERSION=%%v + set OPENCODE_VERSION=!OPENCODE_VERSION:~18,-2! +) + +set OPENCODE_BASE=https://github.com/sst/opencode/releases/download/v%OPENCODE_VERSION% +set OPENCODE_URL=%OPENCODE_BASE%/opencode-windows-%ARCH%.exe +set CHECKSUM_URL=%OPENCODE_BASE%/checksums.txt + +if exist "%BIN_DIR%\opencode.exe" ( + echo [OK] OpenCode binary already exists +) else ( + echo [INFO] Downloading OpenCode v%OPENCODE_VERSION%... + echo Downloading from: %OPENCODE_URL% + + :: Download binary to BIN_DIR + curl -L -o "%BIN_DIR%\opencode.exe.tmp" "%OPENCODE_URL%" + if %ERRORLEVEL% neq 0 ( + echo [ERROR] Download failed! + set /a ERRORS+=1 + goto :skip_opencode + ) + + :: Download checksums + curl -L -o "%BIN_DIR%\checksums.txt" "%CHECKSUM_URL%" + + :: Extract expected checksum + set EXPECTED_HASH= + for /f "tokens=1,2" %%h in ('type "%BIN_DIR%\checksums.txt" ^| findstr /i "opencode-windows-%ARCH%"') do ( + set EXPECTED_HASH=%%h + ) + + :: Calculate actual hash (line 2 from certutil) + set ACTUAL_HASH= + for /f "skip=1 tokens=*" %%h in ('certutil -hashfile "%BIN_DIR%\opencode.exe.tmp" SHA256 ^| findstr /v "CertUtil" ^| findstr /v "hash of"') do ( + set ACTUAL_HASH=%%h + goto :hash_found + ) + :hash_found + + :: Verify and output hashes + echo Expected hash: !EXPECTED_HASH! + echo Actual hash: !ACTUAL_HASH! + + if "!ACTUAL_HASH!"=="!EXPECTED_HASH!" ( + move /Y "%BIN_DIR%\opencode.exe.tmp" "%BIN_DIR%\opencode.exe" + echo [OK] OpenCode downloaded and verified + echo [%date% %time%] OpenCode v%OPENCODE_VERSION% downloaded, checksum verified >> "%TARGET_DIR%\install.log" + ) else ( + echo [ERROR] Checksum mismatch! + del "%BIN_DIR%\opencode.exe.tmp" + set /a ERRORS+=1 + ) +) +:skip_opencode +``` + +**Install-Mac.sh / Install-Linux.sh:** Similar pattern with `opencode-darwin-${ARCH}` and `opencode-linux-${ARCH}`, using `TARGET_DIR/bin` + +**Verification:** Log shows `OpenCode v downloaded, checksum verified` + `ls TARGET_DIR/bin/opencode` exists + +--- + +## C5: Windows Checksum Parsing + +**Included in C4 above.** Key change: + +```batch +:: Parse certutil output - hash is on line 2 +for /f "skip=1 tokens=*" %%h in ('certutil -hashfile "%BIN_DIR%\opencode.exe.tmp" SHA256 ^| findstr /v "CertUtil" ^| findstr /v "hash of"') do ( + set ACTUAL_HASH=%%h + goto :hash_found +) +``` + +**Verification:** Output shows matching hashes: +``` +Expected hash: abc123def456... +Actual hash: abc123def456... +``` + +--- + +## C6: Permission Fallback with TARGET_DIR/BIN_DIR + +**Install-Windows.bat (lines 125-160):** + +```batch +set TARGET_DIR=%SCRIPT_DIR% +set BIN_DIR=%TARGET_DIR%\bin +set NEEDS_FALLBACK=0 + +echo [STEP 2/10] Checking Write Permissions... +echo. + +echo. > "%SCRIPT_DIR%\test-write.tmp" 2>nul +if %ERRORLEVEL% neq 0 ( + echo [WARN] Cannot write to current directory: %SCRIPT_DIR% + echo [INFO] Setting fallback for install outputs... + + set TARGET_DIR=%USERPROFILE%\NomadArch-Install + set BIN_DIR=%TARGET_DIR%\bin + if not exist "%TARGET_DIR%" mkdir "%TARGET_DIR%" + if not exist "%BIN_DIR%" mkdir "%BIN_DIR%" + + echo. > "%TARGET_DIR%\test-write.tmp" 2>nul + if %ERRORLEVEL% neq 0 ( + echo [ERROR] Cannot write to fallback directory either! + set /a ERRORS+=1 + goto :final_check + ) + + echo [OK] Using fallback for outputs: %TARGET_DIR% + echo [%date% %time%] Using fallback: %TARGET_DIR% >> "%TARGET_DIR%\install.log" + set NEEDS_FALLBACK=1 + del "%TARGET_DIR%\test-write.tmp" +) else ( + if not exist "%BIN_DIR%" mkdir "%BIN_DIR%" + del "%SCRIPT_DIR%\test-write.tmp" + echo [OK] Write permissions verified +) + +:: All log writes use TARGET_DIR +set LOG_FILE=%TARGET_DIR%\install.log +``` + +**Install-Mac.sh / Install-Linux.sh:** Similar pattern with `TARGET_DIR=$HOME/.nomadarch-install`, `BIN_DIR=$TARGET_DIR/bin` + +**Verification:** Run from read-only directory, output shows `Using fallback for outputs: C:\Users\xxx\NomadArch-Install` + +--- + +## C7: Health Check Path Corrections + +**Install-Windows.bat (health check section):** + +```diff +:: UI health check +- if exist "%SCRIPT_DIR%\packages\ui\src\renderer\dist" ( ++ if exist "%SCRIPT_DIR%\packages\ui\dist\index.html" ( + echo [OK] UI build directory exists +) else ( +- echo [ERROR] UI build directory not found ++ echo [ERROR] UI build directory not found at packages\ui\dist + set /a HEALTH_ERRORS+=1 +) + +:: Electron health check +- if exist "%SCRIPT_DIR%\packages\electron-app\dist\main.js" ( ++ if exist "%SCRIPT_DIR%\packages\electron-app\dist\main\main.js" ( + echo [OK] Electron main.js exists +) else ( + echo [WARN] Electron build not found (will build on launch) +) +``` + +**Install-Mac.sh / Install-Linux.sh:** Same logic with shell syntax + +**Verification:** Health check output: +``` +[OK] UI build directory exists +[OK] Electron main.js exists +``` + +--- + +## C8: Launch-Dev-Windows Electron Path Fix + +**Launch-Dev-Windows.bat line 162:** + +```diff +- if not exist "electron-app\dist\main.js" ( ++ if not exist "packages\electron-app\dist\main\main.js" ( +``` + +**Verification:** `grep -n "electron-app" Launch-Dev-Windows.bat` shows no `electron-app\` references remaining + +--- + +## Execution Order + +1. C6 (TARGET_DIR/BIN_DIR) - Foundation for C4 +2. C7 (Health checks) - Independent path fixes +3. C1 (UI paths) - Quick path replacements +4. C8 (Launch-Dev-Windows) - Quick path fix +5. C2 (Vite port) - Includes new file creation +6. C3 (Server port) - Quick env var changes +7. C4 (OpenCode download) - Depends on C6, includes C5 +8. **Run build** for C1/C7 verification + +--- + +## Verification Commands to Run + +| Fix | Command | Expected Output | +|------|----------|----------------| +| C1 | `dir packages\ui\dist` | Shows `index.html`, `assets/` | +| C2 | Run Launch-Dev, check Vite log | `Local: http://localhost:3001` | +| C3 | Run launcher, check server log | `CodeNomad Server is ready at http://127.0.0.1:3001` | +| C4 | Run install, grep log | `OpenCode v downloaded, checksum verified` | +| C5 | Run install, check log | Hashes match in output | +| C6 | Run from read-only dir | `Using fallback: C:\Users\xxx\NomadArch-Install` | +| C7 | Run install, check output | `UI build directory exists` + `Electron main.js exists` | +| C8 | `grep -n "electron-app" Launch-Dev-Windows.bat` | Only `packages\electron-app` or commented lines | + +--- + +## Files Modified/Created + +| File | Action | +|------|--------| +| Install-Windows.bat | Edit (C1, C4, C5, C6, C7) | +| Install-Mac.sh | Edit (C1, C4, C6, C7) | +| Install-Linux.sh | Edit (C1, C4, C6, C7) | +| Launch-Windows.bat | Edit (C1, C3) | +| Launch-Dev-Windows.bat | Edit (C1, C2, C3, C8) | +| Launch-Unix.sh | Edit (C1, C3) | +| Launch-Dev-Unix.sh | CREATE (C2) | +| packages/ui/vite.config.ts | Edit (C2) | \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3017aae --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,20 @@ +# AGENT NOTES + +## Styling Guidelines +- Reuse the existing token & utility layers before introducing new CSS variables or custom properties. Extend `src/styles/tokens.css` / `src/styles/utilities.css` if a shared pattern is needed. +- Keep aggregate entry files (e.g., `src/styles/controls.css`, `messaging.css`, `panels.css`) lean—they should only `@import` feature-specific subfiles located inside `src/styles/{components|messaging|panels}`. +- When adding new component styles, place them beside their peers in the scoped subdirectory (e.g., `src/styles/messaging/new-part.css`) and import them from the corresponding aggregator file. +- Prefer smaller, focused style files (≈150 lines or less) over large monoliths. Split by component or feature area if a file grows beyond that size. +- Co-locate reusable UI patterns (buttons, selectors, dropdowns, etc.) under `src/styles/components/` and avoid redefining the same utility classes elsewhere. +- Document any new styling conventions or directory additions in this file so future changes remain consistent. + +## Coding Principles +- Favor KISS by keeping modules narrowly scoped and limiting public APIs to what callers actually need. +- Uphold DRY: share helpers via dedicated modules before copy/pasting logic across stores, components, or scripts. +- Enforce single responsibility; split large files when concerns diverge (state, actions, API, events, etc.). +- Prefer composable primitives (signals, hooks, utilities) over deep inheritance or implicit global state. +- When adding platform integrations (SSE, IPC, SDK), isolate them in thin adapters that surface typed events/actions. + +## Tooling Preferences +- Use the `edit` tool for modifying existing files; prefer it over other editing methods. +- Use the `write` tool only when creating new files from scratch. diff --git a/BUILD.md b/BUILD.md new file mode 100644 index 0000000..0ae1909 --- /dev/null +++ b/BUILD.md @@ -0,0 +1,263 @@ +# Building CodeNomad Binaries + +This guide explains how to build distributable binaries for CodeNomad. + +## Prerequisites + +- **Bun** - Package manager and runtime +- **Node.js** - For electron-builder +- **Electron Builder** - Installed via devDependencies + +## Quick Start + +All commands now run inside the workspace packages. From the repo root you can target the Electron app package directly: + +```bash +npm run build --workspace @neuralnomads/codenomad-electron-app +``` + +### Build for Current Platform (macOS default) + +```bash +bun run build:binaries +``` + +This builds for macOS (Universal - Intel + Apple Silicon) by default. + +## Platform-Specific Builds + +### macOS + +```bash +# Universal (Intel + Apple Silicon) - Recommended +bun run build:mac + +# Intel only (x64) +bun run build:mac-x64 + +# Apple Silicon only (ARM64) +bun run build:mac-arm64 +``` + +**Output formats:** `.dmg`, `.zip` + +### Windows + +```bash +# x64 (64-bit Intel/AMD) +bun run build:win + +# ARM64 (Windows on ARM) +bun run build:win-arm64 +``` + +**Output formats:** `.exe` (NSIS installer), `.zip` + +### Linux + +```bash +# x64 (64-bit) +bun run build:linux + +# ARM64 +bun run build:linux-arm64 +``` + +**Output formats:** `.AppImage`, `.deb`, `.tar.gz` + +### Build All Platforms + +```bash +bun run build:all +``` + +⚠️ **Note:** Cross-platform builds may have limitations. Build on the target platform for best results. + +## Build Process + +The build script performs these steps: + +1. **Build @neuralnomads/codenomad** → Produces the CLI `dist/` bundle (also rebuilds the UI assets it serves) +2. **Compile TypeScript + bundle with Vite** → Electron main, preload, and renderer output in `dist/` +3. **Package with electron-builder** → Platform-specific binaries + +## Output + +Binaries are generated in the `release/` directory: + +``` +release/ +├── CodeNomad-0.1.0-mac-universal.dmg +├── CodeNomad-0.1.0-mac-universal.zip +├── CodeNomad-0.1.0-win-x64.exe +├── CodeNomad-0.1.0-linux-x64.AppImage +└── ... +``` + +## File Naming Convention + +``` +CodeNomad-{version}-{os}-{arch}.{ext} +``` + +- **version**: From package.json (e.g., `0.1.0`) +- **os**: `mac`, `win`, `linux` +- **arch**: `x64`, `arm64`, `universal` +- **ext**: `dmg`, `zip`, `exe`, `AppImage`, `deb`, `tar.gz` + +## Platform Requirements + +### macOS + +- **Build on:** macOS 10.13+ +- **Run on:** macOS 10.13+ +- **Code signing:** Optional (recommended for distribution) + +### Windows + +- **Build on:** Windows 10+, macOS, or Linux +- **Run on:** Windows 10+ +- **Code signing:** Optional (recommended for distribution) + +### Linux + +- **Build on:** Any platform +- **Run on:** Ubuntu 18.04+, Debian 10+, Fedora 32+, Arch +- **Dependencies:** Varies by distro + +## Troubleshooting + +### Build fails on macOS + +```bash +# Install Xcode Command Line Tools +xcode-select --install +``` + +### Build fails on Linux + +```bash +# Install dependencies (Debian/Ubuntu) +sudo apt-get install -y rpm + +# Install dependencies (Fedora) +sudo dnf install -y rpm-build +``` + +### "electron-builder not found" + +```bash +# Install dependencies +bun install +``` + +### Build is slow + +- Use platform-specific builds instead of `build:all` +- Close other applications to free up resources +- Use SSD for faster I/O + +## Development vs Production + +**Development:** + +```bash +bun run dev # Hot reload, no packaging +``` + +**Production:** + +```bash +bun run build:binaries # Full build + packaging +``` + +## CI/CD Integration + +Example GitHub Actions workflow: + +```yaml +name: Build Binaries + +on: + push: + tags: + - "v*" + +jobs: + build-mac: + runs-on: macos-latest + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v1 + - run: bun install + - run: bun run build:mac + + build-win: + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v1 + - run: bun install + - run: bun run build:win + + build-linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: oven-sh/setup-bun@v1 + - run: bun install + - run: bun run build:linux +``` + +## Advanced Configuration + +Edit `package.json` → `build` section to customize: + +- App icon +- Code signing +- Installer options +- File associations +- Auto-update settings + +See [electron-builder docs](https://www.electron.build/) for details. + +## Brand Assets + +- `images/CodeNomad-Icon.png` — primary asset for in-app logo placements and the 1024×1024 master icon used to generate packaged app icons + +To update the binaries: + +1. Run `node scripts/generate-icons.js images/CodeNomad-Icon.png electron/resources` to round the corners and emit fresh `icon.icns`, `icon.ico`, and `icon.png` files. +2. (Optional) Pass `--radius` to tweak the corner curvature or `--name` to change the filename prefix. +3. If you prefer manual control, export `images/CodeNomad-Icon.png` with your tool of choice and place the generated files in `electron/resources/`. + +## Clean Build + +Remove previous builds: + +```bash +rm -rf release/ dist/ +bun run build:binaries +``` + +## FAQ + +**Q: Can I build for Windows on macOS?** +A: Yes, but native binaries (e.g., DMG) require the target OS. + +**Q: How large are the binaries?** +A: Approximately 100-150 MB (includes Electron runtime). + +**Q: Do I need code signing?** +A: Not required, but recommended for public distribution to avoid security warnings. + +**Q: How do I update the version?** +A: Update `version` in `package.json`, then rebuild. + +## Support + +For issues or questions: + +- Check [electron-builder documentation](https://www.electron.build/) +- Open an issue in the repository +- Review existing build logs in `release/` diff --git a/Dev Chat with IDEs/GLM 4.7 Progress Log.txt b/Dev Chat with IDEs/GLM 4.7 Progress Log.txt new file mode 100644 index 0000000..c4063b5 --- /dev/null +++ b/Dev Chat with IDEs/GLM 4.7 Progress Log.txt @@ -0,0 +1,819 @@ +SOLO Coder + +Thought process + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +Fix multix chat auto-scroll for new messages and streaming + +Fixed multix chat auto-scroll for new messages and streaming. Added a createEffect that monitors message changes and automatically scrolls to bottom when: +1. New messages arrive +2. During streaming (when isAgentThinking is true) + +The effect uses requestAnimationFrame and setTimeout(50ms) for smooth scrolling. + +0/1 done + +Fix multix chat auto-scroll for new messages and streaming + +Thought + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +packages\ui\src\stores\session-events.ts + +Thought + +Search codebase for 'scrollToBottom|scrollIntoView|setShouldScroll' + +Thought + +Search codebase for 'addMessage|updateMessage|createMessage' + +Thought + +Search codebase for 'EventContentDelta|EventMessageDone' + +Thought + +Search codebase for 'case.*session\.message|handleMessageUpdate' + +Thought + +Let me look at the sendMessage function and add auto-scroll when new content arrives: + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +Now I can see the issue. There's a comment saying auto-scroll is handled in two places, but the second place (during streaming) is missing. Let me add the auto-scroll effect: + +packages\ui\src\components\chat\multi-task-chat.tsx + +Thought + +multi-task-chat.tsx +packages\ui\src\components\chat\multi-task-chat.tsx + ++ 11 - 1 + +Open Diff + +Thought + +Let me check if scrollToBottom function exists: + +Search codebase for 'function scrollToBottom|const scrollToBottom' + +Thought + +NomadArch +Auto Run +Terminal + +cd "e:\TRAE Playground\NeuralNomadsAi\NomadArch" && npm run typecheck 2>&1 | Select-Object -First 20 + +(TraeAI-8) E:\TRAE Playground\NeuralNomadsAi\NomadArch [0:0] $ cd "e:\TRAE Playground\NeuralNomadsAi\NomadArch" && npm run typecheck 2>&1 | Select-Object -First 20 + +> nomadarch-workspace@0.4.0 typecheck +> npm run typecheck --workspace @nomadarch/ui && npm run typecheck --workspace @nomadarch/nomadarch-electron-app + + +> @nomadarch/ui@0.4.0 typecheck +> tsc --noEmit -p tsconfig.json + + +> @nomadarch/nomadarch-electron-app@0.4.0 typecheck +> tsc --noEmit -p tsconfig.json + +Thought + +1/1 done + +Fix multix chat auto-scroll for new messages and streaming + +## Auto-Scroll Fixed for Multix Chat! +File : packages/ui/src/components/chat/multi-task-chat.tsx:314-323 + +Added a createEffect that monitors message changes and automatically scrolls to the bottom when: + +1. New messages arrive +2. During streaming (when isAgentThinking is true) +The effect uses requestAnimationFrame and setTimeout(50ms) for smooth, performant scrolling. This ensures the chat always shows the latest content as it streams in from the AI. + +### All Issues Fixed: +1. ✅ API Key Dialog - Provider-aware, won't show for local providers like Ollama/Qwen +2. ✅ Multix Menu - Horizontal scroll instead of wrapping, items no longer cut off +3. ✅ Auto-Scroll - Chat automatically scrolls when new messages arrive or streaming + +Artifacts + +1 file changed + 11 - 1 + +Open Diff + +multi-task-chat.tsx +E:\TRAE Playground\NeuralNomadsAi\NomadArch\packages\ui\src\components\chat\multi-task-chat.tsx + ++ 11 - 1 + +Rom283 + +de_modules/@suid/base/BackdropUnstyled/index.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/Cancel.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/CheckBox.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/CheckBoxOutlineBlank.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/IndeterminateCheckBox.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/TextareaAutosize/index.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/ArrowDropDown.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/NativeSelect/NativeSelectInput.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/OutlinedInput/NotchedOutline.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/FirstPage.jsx status=304 durationMs=3 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/NavigateBefore.jsx status=304 durationMs=3 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/NavigateNext.jsx status=304 durationMs=3 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/LastPage.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/Radio/RadioButtonIcon.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/Select/SelectInput.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/Star.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/StarBorder.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/Portal/index.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/PopperUnstyled/index.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/ButtonBase/Ripple.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/Transition/Transition.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/ModalUnstyled/ModalUnstyled.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/BadgeUnstyled/BadgeUnstyled.jsx status=304 durationMs=2 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/MoreHoriz.jsx status=304 durationMs=3 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/BackdropUnstyled/BackdropUnstyled.jsx status=304 durationMs=3 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/TextareaAutosize/TextareaAutosize.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/TextareaAutosize/TextareaAutosizeProps.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/Portal/Portal.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/RadioButtonChecked.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/material/internal/svg-icons/RadioButtonUnchecked.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/node_modules/@suid/base/PopperUnstyled/PopperUnstyled.jsx status=304 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/api/config/app status=200 durationMs=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/api/workspaces status=200 durationMs=1 + +[cli] failed to load CLI view: Error: (-3) loading 'http://localhost:3000/' + +at rejectAndCleanup (node:electron/js2c/browser_init:2:79837) + +at WebContents.finishListener (node:electron/js2c/browser_init:2:79999) + +at WebContents.emit (node:events:519:28) { + +errno: -3, + +code: '', + +url: 'http://localhost:3000/' + +} + +[cli][stdout] [DEBUG] [sse] SSE client connected clientId=1 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/api/workspaces/mjhl6zmm/files?path=. status=200 durationMs=46 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/api/meta status=200 durationMs=114 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=GET url=/@fs/E:/TRAE%20Playground/NeuralNomadsAi/NomadArch/packages/ui/src/images/CodeNomad-Icon.png status=304 durationMs=3 + +[cli][stdout] [INFO] [http] Proxying request url= https://ollama.com/api/tags method=GET + +[cli][stdout] [INFO] [http] Proxying request url= https://ollama.com/api/tags method=GET + +[cli][stdout] [DEBUG] [http] HTTP request completed method=POST url=/api/proxy status=200 durationMs=458 + +[cli][stdout] [INFO] [http] Proxying request url= https://api.z.ai/api/paas/v4/models method=GET + +[cli][stdout] [DEBUG] [http] HTTP request completed method=POST url=/api/proxy status=200 durationMs=366 + +[cli][stdout] [INFO] [http] Proxying request url= https://api.z.ai/api/paas/v4/models method=GET + +[cli][stdout] [DEBUG] [http] HTTP request completed method=POST url=/api/proxy status=200 durationMs=1755 + +[cli][stdout] [DEBUG] [http] HTTP request completed method=POST url=/api/proxy status=200 durationMs=2094 + +Something stuck again, electron opens into black screen and stuck there + +SOLO Coder + +Thought process + +Fix Electron black screen/stuck on load issue + +Fixed Electron black screen/stuck on load issue: + +1. Added retry logic with exponential backoff (up to 5 attempts) for error code -3 (network connection errors) +2. Added 30-second timeout for both preload view and main window load +3. Added user-friendly error screen when max retries are reached +4. Changed `on` to `once` for error event listeners to prevent duplicate listeners +5. Reset retry counter on successful load + +Now when Electron fails to load localhost:3000, it will: +- Automatically retry with increasing delays (1s, 2s, 4s, 8s, 16s) +- Show loading screen during retries +- Display friendly error screen with retry button after max attempts + +0/1 done + +Fix Electron black screen/stuck on load issue + +Thought + +Search codebase for 'failed to load CLI view|loading.*localhost:3000' + +Thought + +packages\electron-app\electron\main\main.ts + +Thought + +Search codebase for 'class.*CliManager|CliManager.*=' + +Thought + +Search codebase for 'import.*cliManager|const cliManager|new.*CliManager' + +Thought + +Search codebase for '**/cli-process*.{ts,tsx,js,jsx}' + +Thought + +Search codebase for 'CliProcessManager|class.*Manager' + +Thought + +packages\electron-app\electron\main\process-manager.ts + +Thought + +packages\electron-app\electron\main\process-manager.ts + +Thought + +Looking at the error failed to load CLI view: Error: (-3) loading 'http://localhost:3000/' , this is a network/connection error in Electron. The issue is that when loadURL fails, the window is left in a black state with no recovery. + +Let me add proper error handling with retry logic and a fallback error screen: + +packages\electron-app\electron\main\main.ts + +Thought + +main.ts +packages\electron-app\electron\main\main.ts + ++ 2 - 0 + +Open Diff + +Thought + +main.ts +packages\electron-app\electron\main\main.ts + ++ 10 - 0 + +Open Diff + +Thought + +main.ts +packages\electron-app\electron\main\main.ts + ++ 108 - 1 + +Open Diff + +Thought + +Search codebase for 'did-finish-load' + +Thought + +Let me also add a timeout handler for the preload view to prevent it from hanging indefinitely: + +packages\electron-app\electron\main\main.ts + +Thought + +main.ts +packages\electron-app\electron\main\main.ts + ++ 9 - 0 + +Open Diff + +Thought + +packages\electron-app\electron\main\main.ts + +Thought + +Let me add a timeout handler for the main window load as well: + +packages\electron-app\electron\main\main.ts + +Thought + +main.ts +packages\electron-app\electron\main\main.ts + ++ 11 - 0 + +Open Diff + +Thought + +NomadArch +Auto Run +Terminal + +cd "e:\TRAE Playground\NeuralNomadsAi\NomadArch" && npm run typecheck 2>&1 | Select-Object -First 30 + +(TraeAI-8) E:\TRAE Playground\NeuralNomadsAi\NomadArch [0:0] $ cd "e:\TRAE Playground\NeuralNomadsAi\NomadArch" && npm run typecheck 2>&1 | Select-Object -First 30 + +> nomadarch-workspace@0.4.0 typecheck +> npm run typecheck --workspace @nomadarch/ui && npm run typecheck --workspace @nomadarch/nomadarch-electron-app + + +> @nomadarch/ui@0.4.0 typecheck +> tsc --noEmit -p tsconfig.json + + +> @nomadarch/nomadarch-electron-app@0.4.0 typecheck +> tsc --noEmit -p tsconfig.json + +Thought + +packages\electron-app\electron\main\main.ts + +Thought + +Let me also fix a potential issue with multiple error listeners being attached to webContents: + +packages\electron-app\electron\main\main.ts + +Thought + +main.ts +packages\electron-app\electron\main\main.ts + ++ 1 - 1 + +Open Diff + +Thought + +NomadArch +Auto Run +Terminal + +cd "e:\TRAE Playground\NeuralNomadsAi\NomadArch" && npm run typecheck 2>&1 | Select-Object -First 30 + +(TraeAI-8) E:\TRAE Playground\NeuralNomadsAi\NomadArch [0:0] $ cd "e:\TRAE Playground\NeuralNomadsAi\NomadArch" && npm run typecheck 2>&1 | Select-Object -First 30 + +> nomadarch-workspace@0.4.0 typecheck +> npm run typecheck --workspace @nomadarch/ui && npm run typecheck --workspace @nomadarch/nomadarch-electron-app + + +> @nomadarch/ui@0.4.0 typecheck +> tsc --noEmit -p tsconfig.json + + +> @nomadarch/nomadarch-electron-app@0.4.0 typecheck +> tsc --noEmit -p tsconfig.json + +Thought + +1/1 done + +Fix Electron black screen/stuck on load issue + +## Electron Black Screen Issue Fixed! +File : packages/electron-app/electron/main/main.ts + +The issue was that when Electron failed to load http://localhost:3000/ (error -3), the window would stay stuck in a black state with no recovery. + +### Changes Made: +1. Retry Logic - Added exponential backoff retry (up to 5 attempts): + + - 1s delay for 1st retry + - 2s delay for 2nd retry + - 4s, 8s, 16s for subsequent retries +2. Timeout Handlers - Added 30-second timeout for both: + + - Preload view (BrowserView) + - Main window load +3. Error Screen - Added user-friendly error screen when max retries reached: + + - Dark theme matching app design + - Clear error message + - Retry button that refreshes the page +4. Event Listener Fix - Changed on to once for error events to prevent duplicate listeners +### How It Works Now: +- ✅ CLI starts → Preloads view in background +- ✅ View fails to load → Auto-retry with backoff +- ✅ Main window fails to load → Auto-retry with backoff +- ✅ After 5 failed attempts → Shows friendly error screen +- ✅ User can click "Retry" button to try again +The app should now handle network issues gracefully instead of showing a black screen forever. + +Artifacts + +1 file changed + 140 - 1 + +Open Diff + +main.ts +E:\TRAE Playground\NeuralNomadsAi\NomadArch\packages\electron-app\electron\main\main.ts + ++ 140 - 1 + +SOLO Builder + +Thought process + +# NomadArch Feature Improvements & UX/UI Enhancements Report +## 1. README SEO & Search Engine Optimization +### What Was Done +Added comprehensive search engine optimization metadata to the top of README.md : + 1.1 HTML Meta Tags +HTML + 1.2 Open Graph Metadata (Facebook/LinkedIn) +HTML + 1.3 Twitter Card Metadata +HTML + 1.4 Schema.org JSON-LD Structured Data +JSON + +### Benefits +- Improved search engine discoverability +- Rich preview cards on social media sharing +- Better indexing by search crawlers +- Enhanced repository ranking on GitHub search +## 2. Repository Branding & Engagement Elements +### What Was Done 2.1 ASCII Logo Enhancement +Added large ASCII art logo at the top of README: + +PlainText + 2.2 GitHub Badges with Auto-Update +Added dynamic badges at the top of README: + +Markdown + 2.3 Call-to-Action Banner +Added engagement banner: + +Markdown + 2.4 Table of Contents +Added navigation links for quick access to sections: + +Markdown + +### Benefits +- Professional repository appearance +- Improved user engagement (stars/forks tracking) +- Quick navigation to relevant sections +- Visual hierarchy and branding +## 3. AI Models & Providers Section (New Section) +### What Was Done +Created a dedicated showcase section highlighting GLM 4.7 and all supported AI providers. + 3.1 GLM 4.7 Spotlight +Markdown + 3.2 Discount Code Integration +Markdown + 3.3 Complete Provider Listings +Created comprehensive tables for each provider: + +Z.AI Models: + +Model Context Window Pricing Best For GLM 4.7 128K $0.50/1M tokens Web development, coding GLM 4.6 128K $0.40/1M tokens General coding GLM 4 128K $0.30/1M tokens Basic tasks + +Anthropic Models: + +Model Context Window Pricing Best For Claude 3.7 Sonnet 200K $3.00/1M tokens Complex reasoning Claude 3.5 Sonnet 200K $3.00/1M tokens Balanced performance Claude 3 Opus 200K $15.00/1M tokens Maximum capability + +OpenAI Models: + +Model Context Window Pricing Best For GPT-5 Preview 128K $10.00/1M tokens Latest capabilities GPT-4.1 128K $5.00/1M tokens Advanced reasoning GPT-4 Turbo 128K $3.00/1M tokens Fast responses + +Google Models: + +Model Context Window Pricing Best For Gemini 2.0 Pro 1M $1.00/1M tokens Large context Gemini 2.0 Flash 1M $0.50/1M tokens Fast processing + +Qwen Models: + +Model Context Window Pricing Best For Qwen 2.5 Coder 32K $0.30/1M tokens Python/JavaScript Qwen 2.5 32K $0.20/1M tokens General coding + +Ollama Models (Local): + +Model Context Window VRAM Best For DeepSeek Coder 16K 4GB Coding specialist Llama 3.1 70B 128K 40GB Maximum capability CodeLlama 16K 8GB Code generation Mistral 7B 32K 6GB Balanced + +### Benefits +- Clear model comparison for users +- Featured model promotion (GLM 4.7) +- Discount code for cost savings +- Comprehensive provider catalog +- Easy model selection based on use case +## 4. Installer Scripts Enhancement +### What Was Done +Enhanced all three platform installers with auto-dependency resolution and user-friendly diagnostics. + 4.1 Install-Windows.bat +Key Features: + +1. ASCII Art Banner +batch + +2. Administrator Privilege Check +batch + +3. Node.js Detection +batch + +4. OpenCode CLI Installation (Primary: npm) +batch + +5. OpenCode CLI Installation (Fallback: GitHub Releases) +batch + +6. Dependency Installation +batch + +7. UI Auto-Build +batch + +8. Step-by-Step Progress +PlainText + 4.2 Install-Linux.sh +Key Features: + +1. Distribution Detection +Bash + +Run + +2. Package-Specific Installation Commands +Bash + +Run + +3. NVM Installation Instructions +Bash + +Run + +4. Architecture Detection +Bash + +Run + +5. Permission Management +Bash + +Run + 4.3 Install-Mac.sh +Key Features: + +1. macOS Version Detection +Bash + +Run + +2. Architecture Detection (Apple Silicon/Intel) +Bash + +Run + +3. Xcode Command Line Tools Check +Bash + +Run + +4. Homebrew Installation Guidance +Bash + +Run + +### Benefits +- Automatic dependency detection and installation +- Platform-specific optimization +- Fallback methods for reliability +- Clear error messages and solutions +- Progress indicators for user feedback +## 5. Launcher Scripts Creation +### What Was Done +Created three launcher scripts with comprehensive diagnostics and auto-fix capabilities. + 5.1 Launch-Windows.bat +Features: + +1. Dependency Checking +batch + +2. OpenCode CLI Detection +batch + +3. UI Auto-Build +batch + +4. Port Availability Check +batch + +5. Electron Launch +``` +call npm run dev:electron +``` +6. Error Recovery +``` +if %ERRORLEVEL% neq 0 ( +    echo. +    echo [ERROR] NomadArch exited with an error! +    echo. +    echo Common solutions: +    echo   1. Check that all dependencies are installed: npm  +    install +    echo   2. Check that the UI is built: cd packages\ui &&  +    npm run build +    echo   3. Check for port conflicts +) +``` 5.2 Launch-Unix.sh (Linux/macOS) +Features: + +1. Cross-Platform Compatibility +``` +#!/bin/bash +cd "$(dirname "$0")" +``` +2. Dependency Checking +``` +if ! command -v node &> /dev/null; then +    echo "[ERROR] Node.js not found!" +    exit 1 +fi +``` +3. Port Detection (lsof) +``` +if lsof -Pi :$SERVER_PORT -sTCP:LISTEN -t >/dev/null 2>&1;  +then +    echo "[WARN] Port $SERVER_PORT is already in use" +    echo "[INFO] To kill it: kill -9 $(lsof -t  +    -i:$SERVER_PORT)" +fi +``` +4. Electron Launch +``` +npm run dev:electron +``` 5.3 Launch-Dev-Windows.bat +Features: + +1. Development Mode Launcher Opens 3 separate terminal windows for independent development: +``` +echo [INFO] This will open 3 separate terminal windows: +echo   1. Backend Server (port 3001) +echo   2. Frontend UI (port 3000) +echo   3. Electron App +``` +2. Independent Service Management +``` +echo [INFO] Starting Backend Server... +start "NomadArch Server" cmd /k "cd /d  +\"%~dp0packages\server\" && npm run dev" + +echo [INFO] Starting Frontend UI... +start "NomadArch UI" cmd /k "cd /d \"%~dp0packages\ui\" &&  +npm run dev" + +echo [INFO] Starting Electron App... +start "NomadArch Electron" cmd /k "cd /d  +\"%~dp0packages\electron-app\" && npm run dev" +``` +3. Clean Shutdown +``` +echo [INFO] Stopping all services... +taskkill /F /FI "WINDOWTITLE eq NomadArch*" >nul 2>&1 +``` +### Benefits +- Automatic dependency validation before launch +- Port conflict detection and resolution guidance +- UI auto-build when missing +- Separate development mode for debugging +- Clear error messages with solutions +## 6. Installation Documentation Updates +### What Was Done +Updated README Installation section with new approach: + 6.1 Primary Method: npm Installation +``` +### Quick Install (Recommended) + +**Windows:** +```bash +npm install -g opencode-ai@latest +``` + +**Linux/macOS:** +```bash +npm install -g opencode-ai@latest +``` +``` 6.2 Fallback Method: GitHub Releases +``` +### Manual Download + +**Windows (x64):** +```bash +curl -L -o opencode.exe https://github.com/sst/opencode/ +releases/latest/download/opencode-windows-x64.zip +unzip opencode-windows-x64.zip +``` + +**Linux (x64):** +```bash +curl -L -o opencode.zip https://github.com/sst/opencode/ +releases/latest/download/opencode-linux-x64.zip +unzip opencode.zip +``` + +**macOS (Apple Silicon):** +```bash +curl -L -o opencode.zip https://github.com/sst/opencode/ +releases/latest/download/opencode-darwin-arm64.zip +unzip opencode.zip +``` +``` 6.3 Architecture-Specific Links +Platform Architecture Download URL Windows x64 opencode-windows-x64.zip Linux x64 opencode-linux-x64.zip Linux ARM64 opencode-linux-arm64.zip macOS x64 opencode-darwin-x64.zip macOS ARM64 opencode-darwin-arm64.zip + +### Benefits +- Clear installation instructions +- Multiple installation methods +- Platform-specific guidance +- Architecture-specific download links +## Summary of All Changes +Category Files Lines Key Improvements README SEO & Branding README.md ~140 Meta tags, badges, ASCII logo, CTA banner AI Models Section README.md ~75 GLM 4.7 spotlight, provider tables, discount code Windows Installer Install-Windows.bat ~200 Auto-dependency, npm primary + ZIP fallback Linux Installer Install-Linux.sh ~200 Distribution detection, NVM support, permissions macOS Installer Install-Mac.sh ~200 Version detection, Apple Silicon support, Xcode check Windows Launcher Launch-Windows.bat ~120 Port checks, auto-build, error recovery Unix Launcher Launch-Unix.sh ~100 Cross-platform, lsof port detection Dev Launcher Launch-Dev-Windows.bat ~100 Multi-terminal, independent services Total 8 files ~1,135 Comprehensive UX/UI enhancements + +Repository URL: https://github.com/roman-ryzenadvanced/NomadArch-v1.0 + +Completed + +47% + +@ SOLO Builder + +You are chatting with SOLO Builder now \ No newline at end of file diff --git a/Install-Linux.sh b/Install-Linux.sh new file mode 100644 index 0000000..b767e6d --- /dev/null +++ b/Install-Linux.sh @@ -0,0 +1,359 @@ +#!/bin/bash + +# NomadArch Installer for Linux +# Version: 0.5.0 - Binary-Free Mode + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TARGET_DIR="$SCRIPT_DIR" +BIN_DIR="$TARGET_DIR/bin" +LOG_FILE="$TARGET_DIR/install.log" +ERRORS=0 +WARNINGS=0 +NEEDS_FALLBACK=0 +BINARY_FREE_MODE=0 + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" +} + +echo "" +echo "NomadArch Installer (Linux)" +echo "Version: 0.5.0 - Binary-Free Mode" +echo "" + +log "Installer started" + +echo "[STEP 1/8] OS and Architecture Detection" +OS_TYPE=$(uname -s) +ARCH_TYPE=$(uname -m) +log "OS: $OS_TYPE" +log "Architecture: $ARCH_TYPE" + +if [[ "$OS_TYPE" != "Linux" ]]; then + echo -e "${RED}[ERROR]${NC} This installer is for Linux. Current OS: $OS_TYPE" + log "ERROR: Not Linux ($OS_TYPE)" + exit 1 +fi + +case "$ARCH_TYPE" in + x86_64) ARCH="x64" ;; + aarch64) ARCH="arm64" ;; + armv7l) ARCH="arm" ;; + *) + echo -e "${RED}[ERROR]${NC} Unsupported architecture: $ARCH_TYPE" + log "ERROR: Unsupported arch $ARCH_TYPE" + exit 1 + ;; +esac + +echo -e "${GREEN}[OK]${NC} OS: Linux" +echo -e "${GREEN}[OK]${NC} Architecture: $ARCH_TYPE" + +if [[ -f /etc/os-release ]]; then + # shellcheck disable=SC1091 + . /etc/os-release + echo -e "${GREEN}[INFO]${NC} Distribution: ${PRETTY_NAME:-unknown}" +fi + +echo "" +echo "[STEP 2/8] Checking write permissions" +mkdir -p "$BIN_DIR" +if ! touch "$SCRIPT_DIR/.install-write-test" 2>/dev/null; then + echo -e "${YELLOW}[WARN]${NC} No write access to $SCRIPT_DIR" + TARGET_DIR="$HOME/.nomadarch-install" + BIN_DIR="$TARGET_DIR/bin" + LOG_FILE="$TARGET_DIR/install.log" + mkdir -p "$BIN_DIR" + if ! touch "$TARGET_DIR/.install-write-test" 2>/dev/null; then + echo -e "${RED}[ERROR]${NC} Cannot write to $TARGET_DIR" + log "ERROR: Write permission denied to fallback" + exit 1 + fi + rm -f "$TARGET_DIR/.install-write-test" + NEEDS_FALLBACK=1 + echo -e "${GREEN}[OK]${NC} Using fallback: $TARGET_DIR" +else + rm -f "$SCRIPT_DIR/.install-write-test" + echo -e "${GREEN}[OK]${NC} Write access OK" +fi + +log "Install target: $TARGET_DIR" + +echo "" +echo "[STEP 3/8] Ensuring system dependencies" + +SUDO="" +if [[ $EUID -ne 0 ]]; then + if command -v sudo >/dev/null 2>&1; then + SUDO="sudo" + else + echo -e "${RED}[ERROR]${NC} sudo is required to install dependencies" + log "ERROR: sudo not found" + exit 1 + fi +fi + +install_packages() { + local manager="$1" + shift + local packages=("$@") + echo -e "${BLUE}[INFO]${NC} Installing via $manager: ${packages[*]}" + case "$manager" in + apt) + $SUDO apt-get update -y + $SUDO apt-get install -y "${packages[@]}" + ;; + dnf) + $SUDO dnf install -y "${packages[@]}" + ;; + yum) + $SUDO yum install -y "${packages[@]}" + ;; + pacman) + $SUDO pacman -Sy --noconfirm "${packages[@]}" + ;; + zypper) + $SUDO zypper -n install "${packages[@]}" + ;; + apk) + $SUDO apk add --no-cache "${packages[@]}" + ;; + *) + return 1 + ;; + esac +} + +PACKAGE_MANAGER="" +if command -v apt-get >/dev/null 2>&1; then + PACKAGE_MANAGER="apt" +elif command -v dnf >/dev/null 2>&1; then + PACKAGE_MANAGER="dnf" +elif command -v yum >/dev/null 2>&1; then + PACKAGE_MANAGER="yum" +elif command -v pacman >/dev/null 2>&1; then + PACKAGE_MANAGER="pacman" +elif command -v zypper >/dev/null 2>&1; then + PACKAGE_MANAGER="zypper" +elif command -v apk >/dev/null 2>&1; then + PACKAGE_MANAGER="apk" +fi + +if [[ -z "$PACKAGE_MANAGER" ]]; then + echo -e "${RED}[ERROR]${NC} No supported package manager found." + echo "Install Node.js, npm, git, and curl manually." + log "ERROR: No package manager found" + exit 1 +fi + +MISSING_PKGS=() +command -v curl >/dev/null 2>&1 || MISSING_PKGS+=("curl") +command -v git >/dev/null 2>&1 || MISSING_PKGS+=("git") + +if ! command -v node >/dev/null 2>&1; then + case "$PACKAGE_MANAGER" in + apt) MISSING_PKGS+=("nodejs" "npm") ;; + dnf|yum) MISSING_PKGS+=("nodejs" "npm") ;; + pacman) MISSING_PKGS+=("nodejs" "npm") ;; + zypper) MISSING_PKGS+=("nodejs18" "npm18") ;; + apk) MISSING_PKGS+=("nodejs" "npm") ;; + *) MISSING_PKGS+=("nodejs") ;; + esac +elif ! command -v npm >/dev/null 2>&1; then + MISSING_PKGS+=("npm") +fi + +if [[ ${#MISSING_PKGS[@]} -gt 0 ]]; then + install_packages "$PACKAGE_MANAGER" "${MISSING_PKGS[@]}" || { + echo -e "${YELLOW}[WARN]${NC} Some packages failed to install. Trying alternative method..." + if ! command -v node >/dev/null 2>&1; then + install_packages "$PACKAGE_MANAGER" "nodejs" || true + fi + } +fi + +if ! command -v node >/dev/null 2>&1; then + echo -e "${RED}[ERROR]${NC} Node.js install failed." + log "ERROR: Node.js still missing" + exit 1 +fi + +NODE_VERSION=$(node --version) +NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d'v' -f2 | cut -d'.' -f1) +echo -e "${GREEN}[OK]${NC} Node.js: $NODE_VERSION" +if [[ $NODE_MAJOR -lt 18 ]]; then + echo -e "${YELLOW}[WARN]${NC} Node.js 18+ is recommended" + ((WARNINGS++)) +fi + +if ! command -v npm >/dev/null 2>&1; then + echo -e "${RED}[ERROR]${NC} npm is not available" + log "ERROR: npm missing after install" + exit 1 +fi +NPM_VERSION=$(npm --version) +echo -e "${GREEN}[OK]${NC} npm: $NPM_VERSION" + +if command -v git >/dev/null 2>&1; then + echo -e "${GREEN}[OK]${NC} Git: $(git --version)" +else + echo -e "${YELLOW}[WARN]${NC} Git not found (optional)" + ((WARNINGS++)) +fi + +echo "" +echo "[STEP 4/8] Installing npm dependencies" +cd "$SCRIPT_DIR" +log "Running npm install" +if ! npm install; then + echo -e "${RED}[ERROR]${NC} npm install failed" + log "ERROR: npm install failed" + exit 1 +fi + +echo -e "${GREEN}[OK]${NC} Dependencies installed" + +echo "" +echo "[STEP 5/8] OpenCode Binary (OPTIONAL - Binary-Free Mode Available)" +echo -e "${BLUE}[INFO]${NC} NomadArch now supports Binary-Free Mode!" +echo -e "${BLUE}[INFO]${NC} You can use the application without OpenCode binary." +echo -e "${BLUE}[INFO]${NC} Free models from OpenCode Zen are available without the binary." +mkdir -p "$BIN_DIR" + +echo "" +read -p "Skip OpenCode binary download? (Y for Binary-Free Mode / N to download) [Y]: " SKIP_CHOICE +SKIP_CHOICE="${SKIP_CHOICE:-Y}" + +if [[ "${SKIP_CHOICE^^}" == "Y" ]]; then + BINARY_FREE_MODE=1 + echo -e "${GREEN}[INFO]${NC} Skipping OpenCode binary - using Binary-Free Mode" + log "Using Binary-Free Mode" +else + OPENCODE_PINNED_VERSION="0.1.44" + OPENCODE_VERSION="$OPENCODE_PINNED_VERSION" + + LATEST_VERSION=$(curl -s --max-time 10 https://api.github.com/repos/sst/opencode/releases/latest 2>/dev/null | grep '"tag_name"' | cut -d'"' -f4 | sed 's/^v//') + if [[ -n "$LATEST_VERSION" ]]; then + echo -e "${BLUE}[INFO]${NC} Latest available: v${LATEST_VERSION}, using pinned: v${OPENCODE_VERSION}" + fi + + OPENCODE_BASE="https://github.com/sst/opencode/releases/download/v${OPENCODE_VERSION}" + OPENCODE_URL="${OPENCODE_BASE}/opencode-linux-${ARCH}" + CHECKSUM_URL="${OPENCODE_BASE}/checksums.txt" + + NEEDS_DOWNLOAD=0 + if [[ -f "$BIN_DIR/opencode" ]]; then + EXISTING_VERSION=$("$BIN_DIR/opencode" --version 2>/dev/null | head -1 || echo "unknown") + if [[ "$EXISTING_VERSION" == *"$OPENCODE_VERSION"* ]] || [[ "$EXISTING_VERSION" != "unknown" ]]; then + echo -e "${GREEN}[OK]${NC} OpenCode binary exists (version: $EXISTING_VERSION)" + else + echo -e "${YELLOW}[WARN]${NC} Existing binary version mismatch, re-downloading..." + NEEDS_DOWNLOAD=1 + fi + else + NEEDS_DOWNLOAD=1 + fi + + if [[ $NEEDS_DOWNLOAD -eq 1 ]]; then + echo -e "${BLUE}[INFO]${NC} Downloading OpenCode v${OPENCODE_VERSION} for ${ARCH}..." + + DOWNLOAD_SUCCESS=0 + for attempt in 1 2 3; do + if curl -L --fail --retry 3 -o "$BIN_DIR/opencode.tmp" "$OPENCODE_URL" 2>/dev/null; then + DOWNLOAD_SUCCESS=1 + break + fi + echo -e "${YELLOW}[WARN]${NC} Download attempt $attempt failed, retrying..." + sleep 2 + done + + if [[ $DOWNLOAD_SUCCESS -eq 0 ]]; then + echo -e "${YELLOW}[WARN]${NC} Failed to download OpenCode binary - using Binary-Free Mode" + BINARY_FREE_MODE=1 + else + if curl -L --fail -o "$BIN_DIR/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then + EXPECTED_HASH=$(grep "opencode-linux-${ARCH}" "$BIN_DIR/checksums.txt" | awk '{print $1}') + ACTUAL_HASH=$(sha256sum "$BIN_DIR/opencode.tmp" | awk '{print $1}') + + if [[ "$ACTUAL_HASH" == "$EXPECTED_HASH" ]]; then + echo -e "${GREEN}[OK]${NC} Checksum verified" + else + echo -e "${YELLOW}[WARN]${NC} Checksum mismatch (may be OK for some versions)" + fi + fi + + mv "$BIN_DIR/opencode.tmp" "$BIN_DIR/opencode" + chmod +x "$BIN_DIR/opencode" + echo -e "${GREEN}[OK]${NC} OpenCode binary installed" + fi + fi +fi + +echo "" +echo "[STEP 6/8] Building UI assets" +if [[ -d "$SCRIPT_DIR/packages/ui/dist" ]]; then + echo -e "${GREEN}[OK]${NC} UI build already exists" +else + echo -e "${BLUE}[INFO]${NC} Building UI" + pushd "$SCRIPT_DIR/packages/ui" >/dev/null + npm run build + popd >/dev/null + echo -e "${GREEN}[OK]${NC} UI assets built" +fi + +echo "" +echo "[STEP 7/8] Post-install health check" +HEALTH_ERRORS=0 + +[[ -f "$SCRIPT_DIR/package.json" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) +[[ -d "$SCRIPT_DIR/packages/ui" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) +[[ -d "$SCRIPT_DIR/packages/server" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) +[[ -f "$SCRIPT_DIR/packages/ui/dist/index.html" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) + +if [[ $HEALTH_ERRORS -eq 0 ]]; then + echo -e "${GREEN}[OK]${NC} Health checks passed" +else + echo -e "${RED}[ERROR]${NC} Health checks failed ($HEALTH_ERRORS)" + ERRORS=$((ERRORS+HEALTH_ERRORS)) +fi + +echo "" +echo "[STEP 8/8] Installation Summary" +echo "" +echo " Install Dir: $TARGET_DIR" +echo " Architecture: $ARCH" +echo " Node.js: $NODE_VERSION" +echo " npm: $NPM_VERSION" +if [[ $BINARY_FREE_MODE -eq 1 ]]; then + echo " Mode: Binary-Free Mode (OpenCode Zen free models available)" +else + echo " Mode: Full Mode (OpenCode binary installed)" +fi +echo " Errors: $ERRORS" +echo " Warnings: $WARNINGS" +echo " Log File: $LOG_FILE" +echo "" + +if [[ $ERRORS -gt 0 ]]; then + echo -e "${RED}[RESULT]${NC} Installation completed with errors" + echo "Review $LOG_FILE for details." +else + echo -e "${GREEN}[RESULT]${NC} Installation completed successfully" + echo "Run: ./Launch-Unix.sh" + echo "" + if [[ $BINARY_FREE_MODE -eq 1 ]]; then + echo -e "${BLUE}NOTE:${NC} Running in Binary-Free Mode." + echo " Free models (GPT-5 Nano, Grok Code, GLM-4.7, etc.) are available." + echo " You can also authenticate with Qwen for additional models." + fi +fi + +exit $ERRORS diff --git a/Install-Mac.sh b/Install-Mac.sh new file mode 100644 index 0000000..4aa724f --- /dev/null +++ b/Install-Mac.sh @@ -0,0 +1,280 @@ +#!/bin/bash + +# NomadArch Installer for macOS +# Version: 0.5.0 - Binary-Free Mode + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TARGET_DIR="$SCRIPT_DIR" +BIN_DIR="$TARGET_DIR/bin" +LOG_FILE="$TARGET_DIR/install.log" +ERRORS=0 +WARNINGS=0 +NEEDS_FALLBACK=0 +BINARY_FREE_MODE=0 + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_FILE" +} + +echo "" +echo "NomadArch Installer (macOS)" +echo "Version: 0.5.0 - Binary-Free Mode" +echo "" + +log "Installer started" + +echo "[STEP 1/8] OS and Architecture Detection" +OS_TYPE=$(uname -s) +ARCH_TYPE=$(uname -m) +log "OS: $OS_TYPE" +log "Architecture: $ARCH_TYPE" + +if [[ "$OS_TYPE" != "Darwin" ]]; then + echo -e "${RED}[ERROR]${NC} This installer is for macOS. Current OS: $OS_TYPE" + log "ERROR: Not macOS ($OS_TYPE)" + exit 1 +fi + +case "$ARCH_TYPE" in + arm64) ARCH="arm64" ;; + x86_64) ARCH="x64" ;; + *) + echo -e "${RED}[ERROR]${NC} Unsupported architecture: $ARCH_TYPE" + log "ERROR: Unsupported arch $ARCH_TYPE" + exit 1 + ;; +esac + +echo -e "${GREEN}[OK]${NC} OS: macOS" +echo -e "${GREEN}[OK]${NC} Architecture: $ARCH_TYPE" + +echo "" +echo "[STEP 2/8] Checking write permissions" +mkdir -p "$BIN_DIR" +if ! touch "$SCRIPT_DIR/.install-write-test" 2>/dev/null; then + echo -e "${YELLOW}[WARN]${NC} No write access to $SCRIPT_DIR" + TARGET_DIR="$HOME/.nomadarch-install" + BIN_DIR="$TARGET_DIR/bin" + LOG_FILE="$TARGET_DIR/install.log" + mkdir -p "$BIN_DIR" + if ! touch "$TARGET_DIR/.install-write-test" 2>/dev/null; then + echo -e "${RED}[ERROR]${NC} Cannot write to $TARGET_DIR" + log "ERROR: Write permission denied to fallback" + exit 1 + fi + rm -f "$TARGET_DIR/.install-write-test" + NEEDS_FALLBACK=1 + echo -e "${GREEN}[OK]${NC} Using fallback: $TARGET_DIR" +else + rm -f "$SCRIPT_DIR/.install-write-test" + echo -e "${GREEN}[OK]${NC} Write access OK" +fi + +log "Install target: $TARGET_DIR" + +echo "" +echo "[STEP 3/8] Ensuring system dependencies" + +if ! command -v curl >/dev/null 2>&1; then + echo -e "${RED}[ERROR]${NC} curl is required but not available" + exit 1 +fi + +if ! command -v brew >/dev/null 2>&1; then + echo -e "${YELLOW}[INFO]${NC} Homebrew not found. Installing..." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +fi + +MISSING_PKGS=() +command -v git >/dev/null 2>&1 || MISSING_PKGS+=("git") +command -v node >/dev/null 2>&1 || MISSING_PKGS+=("node") + +if [[ ${#MISSING_PKGS[@]} -gt 0 ]]; then + echo -e "${BLUE}[INFO]${NC} Installing: ${MISSING_PKGS[*]}" + brew install "${MISSING_PKGS[@]}" +fi + +if ! command -v node >/dev/null 2>&1; then + echo -e "${RED}[ERROR]${NC} Node.js install failed" + exit 1 +fi + +NODE_VERSION=$(node --version) +NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d'v' -f2 | cut -d'.' -f1) +echo -e "${GREEN}[OK]${NC} Node.js: $NODE_VERSION" +if [[ $NODE_MAJOR -lt 18 ]]; then + echo -e "${YELLOW}[WARN]${NC} Node.js 18+ is recommended" + ((WARNINGS++)) +fi + +if ! command -v npm >/dev/null 2>&1; then + echo -e "${RED}[ERROR]${NC} npm is not available" + exit 1 +fi +NPM_VERSION=$(npm --version) +echo -e "${GREEN}[OK]${NC} npm: $NPM_VERSION" + +if command -v git >/dev/null 2>&1; then + echo -e "${GREEN}[OK]${NC} Git: $(git --version)" +else + echo -e "${YELLOW}[WARN]${NC} Git not found (optional)" + ((WARNINGS++)) +fi + +echo "" +echo "[STEP 4/8] Installing npm dependencies" +cd "$SCRIPT_DIR" +log "Running npm install" +if ! npm install; then + echo -e "${RED}[ERROR]${NC} npm install failed" + log "ERROR: npm install failed" + exit 1 +fi + +echo -e "${GREEN}[OK]${NC} Dependencies installed" + +echo "" +echo "[STEP 5/8] OpenCode Binary (OPTIONAL - Binary-Free Mode Available)" +echo -e "${BLUE}[INFO]${NC} NomadArch now supports Binary-Free Mode!" +echo -e "${BLUE}[INFO]${NC} You can use the application without OpenCode binary." +echo -e "${BLUE}[INFO]${NC} Free models from OpenCode Zen are available without the binary." +mkdir -p "$BIN_DIR" + +echo "" +read -p "Skip OpenCode binary download? (Y for Binary-Free Mode / N to download) [Y]: " SKIP_CHOICE +SKIP_CHOICE="${SKIP_CHOICE:-Y}" + +if [[ "${SKIP_CHOICE^^}" == "Y" ]]; then + BINARY_FREE_MODE=1 + echo -e "${GREEN}[INFO]${NC} Skipping OpenCode binary - using Binary-Free Mode" + log "Using Binary-Free Mode" +else + # Pin to a specific known-working version + OPENCODE_PINNED_VERSION="0.1.44" + OPENCODE_VERSION="$OPENCODE_PINNED_VERSION" + + LATEST_VERSION=$(curl -s --max-time 10 https://api.github.com/repos/sst/opencode/releases/latest 2>/dev/null | grep '"tag_name"' | cut -d'"' -f4 | sed 's/^v//') + if [[ -n "$LATEST_VERSION" ]]; then + echo -e "${BLUE}[INFO]${NC} Latest available: v${LATEST_VERSION}, using pinned: v${OPENCODE_VERSION}" + fi + + OPENCODE_BASE="https://github.com/sst/opencode/releases/download/v${OPENCODE_VERSION}" + OPENCODE_URL="${OPENCODE_BASE}/opencode-darwin-${ARCH}" + CHECKSUM_URL="${OPENCODE_BASE}/checksums.txt" + + NEEDS_DOWNLOAD=0 + if [[ -f "$BIN_DIR/opencode" ]]; then + EXISTING_VERSION=$("$BIN_DIR/opencode" --version 2>/dev/null | head -1 || echo "unknown") + if [[ "$EXISTING_VERSION" == *"$OPENCODE_VERSION"* ]] || [[ "$EXISTING_VERSION" != "unknown" ]]; then + echo -e "${GREEN}[OK]${NC} OpenCode binary exists (version: $EXISTING_VERSION)" + else + echo -e "${YELLOW}[WARN]${NC} Existing binary version mismatch, re-downloading..." + NEEDS_DOWNLOAD=1 + fi + else + NEEDS_DOWNLOAD=1 + fi + + if [[ $NEEDS_DOWNLOAD -eq 1 ]]; then + echo -e "${BLUE}[INFO]${NC} Downloading OpenCode v${OPENCODE_VERSION} for ${ARCH}..." + + DOWNLOAD_SUCCESS=0 + for attempt in 1 2 3; do + if curl -L --fail --retry 3 -o "$BIN_DIR/opencode.tmp" "$OPENCODE_URL" 2>/dev/null; then + DOWNLOAD_SUCCESS=1 + break + fi + echo -e "${YELLOW}[WARN]${NC} Download attempt $attempt failed, retrying..." + sleep 2 + done + + if [[ $DOWNLOAD_SUCCESS -eq 0 ]]; then + echo -e "${YELLOW}[WARN]${NC} Failed to download OpenCode binary - using Binary-Free Mode" + BINARY_FREE_MODE=1 + else + if curl -L --fail -o "$BIN_DIR/checksums.txt" "$CHECKSUM_URL" 2>/dev/null; then + EXPECTED_HASH=$(grep "opencode-darwin-${ARCH}" "$BIN_DIR/checksums.txt" | awk '{print $1}') + ACTUAL_HASH=$(shasum -a 256 "$BIN_DIR/opencode.tmp" | awk '{print $1}') + + if [[ "$ACTUAL_HASH" == "$EXPECTED_HASH" ]]; then + echo -e "${GREEN}[OK]${NC} Checksum verified" + else + echo -e "${YELLOW}[WARN]${NC} Checksum mismatch (may be OK for some versions)" + fi + fi + + mv "$BIN_DIR/opencode.tmp" "$BIN_DIR/opencode" + chmod +x "$BIN_DIR/opencode" + echo -e "${GREEN}[OK]${NC} OpenCode binary installed" + fi + fi +fi + +echo "" +echo "[STEP 6/8] Building UI assets" +if [[ -d "$SCRIPT_DIR/packages/ui/dist" ]]; then + echo -e "${GREEN}[OK]${NC} UI build already exists" +else + echo -e "${BLUE}[INFO]${NC} Building UI" + pushd "$SCRIPT_DIR/packages/ui" >/dev/null + npm run build + popd >/dev/null + echo -e "${GREEN}[OK]${NC} UI assets built" +fi + +echo "" +echo "[STEP 7/8] Post-install health check" +HEALTH_ERRORS=0 + +[[ -f "$SCRIPT_DIR/package.json" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) +[[ -d "$SCRIPT_DIR/packages/ui" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) +[[ -d "$SCRIPT_DIR/packages/server" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) +[[ -f "$SCRIPT_DIR/packages/ui/dist/index.html" ]] || HEALTH_ERRORS=$((HEALTH_ERRORS+1)) + +if [[ $HEALTH_ERRORS -eq 0 ]]; then + echo -e "${GREEN}[OK]${NC} Health checks passed" +else + echo -e "${RED}[ERROR]${NC} Health checks failed ($HEALTH_ERRORS)" + ERRORS=$((ERRORS+HEALTH_ERRORS)) +fi + +echo "" +echo "[STEP 8/8] Installation Summary" +echo "" +echo " Install Dir: $TARGET_DIR" +echo " Architecture: $ARCH" +echo " Node.js: $NODE_VERSION" +echo " npm: $NPM_VERSION" +if [[ $BINARY_FREE_MODE -eq 1 ]]; then + echo " Mode: Binary-Free Mode (OpenCode Zen free models available)" +else + echo " Mode: Full Mode (OpenCode binary installed)" +fi +echo " Errors: $ERRORS" +echo " Warnings: $WARNINGS" +echo " Log File: $LOG_FILE" +echo "" + +if [[ $ERRORS -gt 0 ]]; then + echo -e "${RED}[RESULT]${NC} Installation completed with errors" + echo "Review $LOG_FILE for details." +else + echo -e "${GREEN}[RESULT]${NC} Installation completed successfully" + echo "Run: ./Launch-Unix.sh" + echo "" + if [[ $BINARY_FREE_MODE -eq 1 ]]; then + echo -e "${BLUE}NOTE:${NC} Running in Binary-Free Mode." + echo " Free models (GPT-5 Nano, Grok Code, GLM-4.7, etc.) are available." + echo " You can also authenticate with Qwen for additional models." + fi +fi + +exit $ERRORS diff --git a/Install-Windows.bat b/Install-Windows.bat new file mode 100644 index 0000000..f472a56 --- /dev/null +++ b/Install-Windows.bat @@ -0,0 +1,267 @@ +@echo off +setlocal enabledelayedexpansion + +title NomadArch Installer + +echo. +echo NomadArch Installer (Windows) +echo Version: 0.5.0 - Binary-Free Mode +echo. + +set SCRIPT_DIR=%~dp0 +set SCRIPT_DIR=%SCRIPT_DIR:~0,-1% +set TARGET_DIR=%SCRIPT_DIR% +set BIN_DIR=%TARGET_DIR%\bin +set LOG_FILE=%TARGET_DIR%\install.log +set TEMP_DIR=%TARGET_DIR%\.install-temp + +set ERRORS=0 +set WARNINGS=0 +set NEEDS_FALLBACK=0 +set SKIP_OPENCODE=0 + +echo [%date% %time%] Installer started >> "%LOG_FILE%" + +echo [STEP 1/8] OS and Architecture Detection + +REM Use PowerShell for architecture detection (works on all Windows versions) +for /f "tokens=*" %%i in ('powershell -NoProfile -Command "[System.Environment]::Is64BitOperatingSystem"') do set IS64BIT=%%i +if /i "%IS64BIT%"=="True" ( + set ARCH=x64 +) else ( + set ARCH=x86 +) +echo [OK] Architecture: %ARCH% + +echo. +echo [STEP 2/8] Checking write permissions +if not exist "%BIN_DIR%" mkdir "%BIN_DIR%" 2>nul +if not exist "%TEMP_DIR%" mkdir "%TEMP_DIR%" 2>nul + +echo. > "%SCRIPT_DIR%\test-write.tmp" 2>nul +if !ERRORLEVEL! neq 0 ( + echo [WARN] Cannot write to current directory: %SCRIPT_DIR% + set TARGET_DIR=%USERPROFILE%\NomadArch-Install + set BIN_DIR=!TARGET_DIR!\bin + set LOG_FILE=!TARGET_DIR!\install.log + set TEMP_DIR=!TARGET_DIR!\.install-temp + if not exist "!TARGET_DIR!" mkdir "!TARGET_DIR!" + if not exist "!BIN_DIR!" mkdir "!BIN_DIR!" + if not exist "!TEMP_DIR!" mkdir "!TEMP_DIR!" + echo. > "!TARGET_DIR!\test-write.tmp" 2>nul + if !ERRORLEVEL! neq 0 ( + echo [ERROR] Cannot write to fallback directory: !TARGET_DIR! + echo [%date% %time%] ERROR: Write permission denied >> "%LOG_FILE%" + set /a ERRORS+=1 + goto :SUMMARY + ) + del "!TARGET_DIR!\test-write.tmp" + set NEEDS_FALLBACK=1 + echo [OK] Using fallback: !TARGET_DIR! +) else ( + del "%SCRIPT_DIR%\test-write.tmp" + echo [OK] Write permissions verified +) + +echo. +echo [STEP 3/8] Ensuring system dependencies + +set WINGET_AVAILABLE=0 +where winget >nul 2>&1 +if !ERRORLEVEL! equ 0 set WINGET_AVAILABLE=1 + +set CHOCO_AVAILABLE=0 +where choco >nul 2>&1 +if !ERRORLEVEL! equ 0 set CHOCO_AVAILABLE=1 + +set DOWNLOAD_CMD=powershell +where curl >nul 2>&1 +if !ERRORLEVEL! equ 0 set DOWNLOAD_CMD=curl + +where node >nul 2>&1 +if !ERRORLEVEL! neq 0 ( + echo [INFO] Node.js not found. Attempting to install... + if !WINGET_AVAILABLE! equ 1 ( + winget install -e --id OpenJS.NodeJS.LTS --accept-source-agreements --accept-package-agreements + ) else if !CHOCO_AVAILABLE! equ 1 ( + choco install nodejs-lts -y + ) else ( + echo [ERROR] No supported package manager found. + echo Please install Node.js LTS from https://nodejs.org/ + set /a ERRORS+=1 + goto :SUMMARY + ) +) + +where node >nul 2>&1 +if !ERRORLEVEL! neq 0 ( + echo [ERROR] Node.js install failed or requires a new terminal session. + set /a ERRORS+=1 + goto :SUMMARY +) + +for /f "tokens=*" %%i in ('node --version') do set NODE_VERSION=%%i +echo [OK] Node.js: %NODE_VERSION% + +where npm >nul 2>&1 +if !ERRORLEVEL! neq 0 ( + echo [ERROR] npm not found after Node.js install. + set /a ERRORS+=1 + goto :SUMMARY +) + +for /f "tokens=*" %%i in ('npm --version') do set NPM_VERSION=%%i +echo [OK] npm: %NPM_VERSION% + +where git >nul 2>&1 +if !ERRORLEVEL! neq 0 ( + echo [INFO] Git not found. Attempting to install... + if !WINGET_AVAILABLE! equ 1 ( + winget install -e --id Git.Git --accept-source-agreements --accept-package-agreements + ) else if !CHOCO_AVAILABLE! equ 1 ( + choco install git -y + ) else ( + echo [WARN] Git not installed - optional + set /a WARNINGS+=1 + ) +) else ( + for /f "tokens=*" %%i in ('git --version') do set GIT_VERSION=%%i + echo [OK] Git: !GIT_VERSION! +) + +echo. +echo [STEP 4/8] Installing npm dependencies +cd /d "%SCRIPT_DIR%" +echo [%date% %time%] Running npm install >> "%LOG_FILE%" +call npm install +if !ERRORLEVEL! neq 0 ( + echo [ERROR] npm install failed! + echo [%date% %time%] ERROR: npm install failed >> "%LOG_FILE%" + set /a ERRORS+=1 + goto :SUMMARY +) +echo [OK] Dependencies installed + +echo. +echo [STEP 5/8] OpenCode Binary - OPTIONAL +echo. +echo [INFO] NomadArch now supports Binary-Free Mode! +echo [INFO] You can use the application without OpenCode binary. +echo [INFO] Free models from OpenCode Zen are available without the binary. +echo. +if not exist "%BIN_DIR%" mkdir "%BIN_DIR%" 2>nul + +set /p SKIP_CHOICE="Skip OpenCode binary download? (Y for Binary-Free / N to download) [Y]: " +if /i "!SKIP_CHOICE!"=="" set SKIP_CHOICE=Y +if /i "!SKIP_CHOICE!"=="Y" goto :skip_opencode_download + +REM Download OpenCode binary +echo [INFO] Fetching OpenCode version info... +for /f "delims=" %%v in ('powershell -NoProfile -Command "try { (Invoke-WebRequest -UseBasicParsing https://api.github.com/repos/sst/opencode/releases/latest).Content | ConvertFrom-Json | Select-Object -ExpandProperty tag_name } catch { 'v0.1.44' }"') do set OPENCODE_VERSION=%%v +set OPENCODE_VERSION=!OPENCODE_VERSION:v=! + +set OPENCODE_BASE=https://github.com/sst/opencode/releases/download/v!OPENCODE_VERSION! +set OPENCODE_URL=!OPENCODE_BASE!/opencode-windows-%ARCH%.exe +set CHECKSUM_URL=!OPENCODE_BASE!/checksums.txt + +if exist "%BIN_DIR%\opencode.exe" ( + echo [OK] OpenCode binary already exists + echo [%date% %time%] OpenCode binary exists, skipping download >> "%LOG_FILE%" + goto :opencode_done +) + +echo [INFO] Downloading OpenCode v!OPENCODE_VERSION!... +if "!DOWNLOAD_CMD!"=="curl" ( + curl -L -o "%BIN_DIR%\opencode.exe.tmp" "!OPENCODE_URL!" +) else ( + powershell -NoProfile -Command "Invoke-WebRequest -Uri '!OPENCODE_URL!' -OutFile '%BIN_DIR%\opencode.exe.tmp'" +) + +if exist "%BIN_DIR%\opencode.exe.tmp" ( + move /Y "%BIN_DIR%\opencode.exe.tmp" "%BIN_DIR%\opencode.exe" >nul + echo [OK] OpenCode downloaded +) else ( + echo [WARN] OpenCode download failed - using Binary-Free Mode instead + set SKIP_OPENCODE=1 +) +goto :opencode_done + +:skip_opencode_download +set SKIP_OPENCODE=1 +echo [INFO] Skipping OpenCode binary - using Binary-Free Mode +echo [%date% %time%] Using Binary-Free Mode >> "%LOG_FILE%" + +:opencode_done + +echo. +echo [STEP 6/8] Building UI assets +if exist "%SCRIPT_DIR%\packages\ui\dist\index.html" ( + echo [OK] UI build already exists +) else ( + echo [INFO] Building UI assets... + pushd packages\ui + call npm run build + if !ERRORLEVEL! neq 0 ( + echo [ERROR] UI build failed! + popd + set /a ERRORS+=1 + goto :SUMMARY + ) + popd + echo [OK] UI assets built successfully +) + +echo. +echo [STEP 7/8] Post-install health check +set HEALTH_ERRORS=0 + +if not exist "%SCRIPT_DIR%\package.json" set /a HEALTH_ERRORS+=1 +if not exist "%SCRIPT_DIR%\packages\ui" set /a HEALTH_ERRORS+=1 +if not exist "%SCRIPT_DIR%\packages\server" set /a HEALTH_ERRORS+=1 +if not exist "%SCRIPT_DIR%\packages\ui\dist\index.html" set /a HEALTH_ERRORS+=1 + +if !HEALTH_ERRORS! equ 0 ( + echo [OK] Health checks passed +) else ( + echo [ERROR] Health checks failed: !HEALTH_ERRORS! issues + set /a ERRORS+=!HEALTH_ERRORS! +) + +echo. +echo [STEP 8/8] Installation Summary +echo. +echo Install Dir: %TARGET_DIR% +echo Architecture: %ARCH% +echo Node.js: %NODE_VERSION% +echo npm: %NPM_VERSION% +if !SKIP_OPENCODE! equ 1 ( + echo Mode: Binary-Free Mode +) else ( + echo Mode: Full Mode with OpenCode binary +) +echo Errors: !ERRORS! +echo Warnings: !WARNINGS! +echo Log File: %LOG_FILE% +echo. + +:SUMMARY +if !ERRORS! gtr 0 ( + echo [RESULT] Installation completed with errors. + echo Review the log: %LOG_FILE% + echo. + echo If Node.js was just installed, open a new terminal and run this installer again. +) else ( + echo [RESULT] Installation completed successfully. + echo Run Launch-Windows.bat to start the application. + echo. + if !SKIP_OPENCODE! equ 1 ( + echo NOTE: Running in Binary-Free Mode. + echo Free models: GPT-5 Nano, Grok Code, GLM-4.7, etc. + echo You can also authenticate with Qwen for additional models. + ) +) + +echo. +echo Press any key to exit... +pause >nul +exit /b !ERRORS! diff --git a/Launch-Dev-Unix.sh b/Launch-Dev-Unix.sh new file mode 100644 index 0000000..bacf4c5 --- /dev/null +++ b/Launch-Dev-Unix.sh @@ -0,0 +1,152 @@ +#!/bin/bash + +# NomadArch Development Launcher for macOS and Linux +# Version: 0.4.0 + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +ERRORS=0 +WARNINGS=0 +AUTO_FIXED=0 + +echo "" +echo "NomadArch Development Launcher (macOS/Linux)" +echo "Version: 0.4.0" +echo "" + +echo "[PREFLIGHT 1/6] Checking Dependencies..." + +if ! command -v node &> /dev/null; then + echo -e "${YELLOW}[WARN]${NC} Node.js not found. Running installer..." + if [[ "$OSTYPE" == "darwin"* ]]; then + bash "$SCRIPT_DIR/Install-Mac.sh" + else + bash "$SCRIPT_DIR/Install-Linux.sh" + fi + echo -e "${BLUE}[INFO]${NC} If Node.js was installed, open a new terminal and run Launch-Dev-Unix.sh again." + exit 1 +fi + +NODE_VERSION=$(node --version) +echo -e "${GREEN}[OK]${NC} Node.js: $NODE_VERSION" + +if ! command -v npm &> /dev/null; then + echo -e "${RED}[ERROR]${NC} npm not found!" + exit 1 +fi + +NPM_VERSION=$(npm --version) +echo -e "${GREEN}[OK]${NC} npm: $NPM_VERSION" + +echo "" +echo "[PREFLIGHT 2/6] Installing dependencies if needed..." + +if [[ ! -d "node_modules" ]]; then + echo -e "${YELLOW}[INFO]${NC} Dependencies not installed. Installing now..." + npm install + echo -e "${GREEN}[OK]${NC} Dependencies installed (auto-fix)" + ((AUTO_FIXED++)) +fi + +echo "" +echo "[PREFLIGHT 3/6] Finding Available Ports..." + +DEFAULT_SERVER_PORT=3001 +DEFAULT_UI_PORT=3000 +SERVER_PORT=$DEFAULT_SERVER_PORT +UI_PORT=$DEFAULT_UI_PORT + +for port in {3001..3050}; do + # Try lsof first, then ss, then netstat + if command -v lsof &> /dev/null; then + if ! lsof -i :$port -sTCP:LISTEN -t > /dev/null 2>&1; then + SERVER_PORT=$port + break + fi + elif command -v ss &> /dev/null; then + if ! ss -tuln | grep -q ":$port "; then + SERVER_PORT=$port + break + fi + elif command -v netstat &> /dev/null; then + if ! netstat -tuln | grep -q ":$port "; then + SERVER_PORT=$port + break + fi + else + SERVER_PORT=$port + break + fi +done + +for port in {3000..3050}; do + # Try lsof first, then ss, then netstat + if command -v lsof &> /dev/null; then + if ! lsof -i :$port -sTCP:LISTEN -t > /dev/null 2>&1; then + UI_PORT=$port + break + fi + elif command -v ss &> /dev/null; then + if ! ss -tuln | grep -q ":$port "; then + UI_PORT=$port + break + fi + elif command -v netstat &> /dev/null; then + if ! netstat -tuln | grep -q ":$port "; then + UI_PORT=$port + break + fi + else + UI_PORT=$port + break + fi +done + +echo -e "${GREEN}[OK]${NC} Server port: $SERVER_PORT" +echo -e "${GREEN}[OK]${NC} UI port: $UI_PORT" + +echo "" +echo "[PREFLIGHT 4/6] Launch Summary" + +echo -e "${BLUE}[STATUS]${NC}" +echo "" +echo " Node.js: $NODE_VERSION" +echo " npm: $NPM_VERSION" +echo " Auto-fixes applied: $AUTO_FIXED" +echo " Warnings: $WARNINGS" +echo " Errors: $ERRORS" +echo " Server Port: $SERVER_PORT" +echo " UI Port: $UI_PORT" +echo "" + +echo "" +echo "[PREFLIGHT 5/6] Starting services..." +echo "" + +export CLI_PORT=$SERVER_PORT +export VITE_PORT=$UI_PORT + +echo -e "${GREEN}[INFO]${NC} Starting backend server..." +nohup bash -c "cd '$SCRIPT_DIR/packages/server' && npm run dev" >/dev/null 2>&1 & + +sleep 2 + +echo -e "${GREEN}[INFO]${NC} Starting UI server..." +nohup bash -c "cd '$SCRIPT_DIR/packages/ui' && npm run dev -- --port $UI_PORT" >/dev/null 2>&1 & + +sleep 2 + +echo -e "${GREEN}[INFO]${NC} Starting Electron app..." +npm run dev:electron + +echo "" +echo "[PREFLIGHT 6/6] Done." diff --git a/Launch-Dev-Windows.bat b/Launch-Dev-Windows.bat new file mode 100644 index 0000000..5d659f2 --- /dev/null +++ b/Launch-Dev-Windows.bat @@ -0,0 +1,192 @@ +@echo off +setlocal enabledelayedexpansion + +title NomadArch Development Launcher +color 0B + +echo. +echo NomadArch Development Launcher (Windows) +echo Version: 0.5.0 - Binary-Free Mode +echo. + +set SCRIPT_DIR=%~dp0 +set SCRIPT_DIR=%SCRIPT_DIR:~0,-1% +cd /d "%SCRIPT_DIR%" + +set ERRORS=0 +set WARNINGS=0 +set AUTO_FIXED=0 +set BINARY_FREE_MODE=0 + +echo [PREFLIGHT 1/7] Checking Dependencies... + +where node >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [WARN] Node.js not found. Running installer... + call "%SCRIPT_DIR%\Install-Windows.bat" + echo [INFO] If Node.js was installed, open a new terminal and run Launch-Dev-Windows.bat again. + pause + exit /b 1 +) + +for /f "tokens=*" %%i in ('node --version') do set NODE_VERSION=%%i +echo [OK] Node.js: %NODE_VERSION% + +where npm >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [ERROR] npm not found! + pause + exit /b 1 +) + +for /f "tokens=*" %%i in ('npm --version') do set NPM_VERSION=%%i +echo [OK] npm: %NPM_VERSION% + +echo. +echo [PREFLIGHT 2/7] Checking for OpenCode CLI... + +where opencode >nul 2>&1 +if %ERRORLEVEL% equ 0 ( + echo [OK] OpenCode CLI in PATH - Full Mode + goto :opencode_check_done +) + +if exist "bin\opencode.exe" ( + echo [OK] OpenCode binary in bin/ - Full Mode + goto :opencode_check_done +) + +echo [INFO] OpenCode CLI not found - Using Binary-Free Mode +echo [INFO] Free models: GPT-5 Nano, Grok Code, GLM-4.7 via OpenCode Zen +set BINARY_FREE_MODE=1 + +:opencode_check_done + +echo. +echo [PREFLIGHT 3/7] Checking Dependencies... + +if not exist "node_modules" ( + echo [INFO] Dependencies not installed. Installing now... + call npm install + if !ERRORLEVEL! neq 0 ( + echo [ERROR] Dependency installation failed! + pause + exit /b 1 + ) + echo [OK] Dependencies installed + set /a AUTO_FIXED+=1 +) else ( + echo [OK] Dependencies found +) + +echo. +echo [PREFLIGHT 4/7] Finding Available Ports... + +set DEFAULT_SERVER_PORT=3001 +set DEFAULT_UI_PORT=3000 +set SERVER_PORT=%DEFAULT_SERVER_PORT% +set UI_PORT=%DEFAULT_UI_PORT% + +for /l %%p in (%DEFAULT_SERVER_PORT%,1,3050) do ( + netstat -ano | findstr ":%%p " | findstr "LISTENING" >nul + if !ERRORLEVEL! neq 0 ( + set SERVER_PORT=%%p + goto :server_port_found + ) +) +:server_port_found + +for /l %%p in (%DEFAULT_UI_PORT%,1,3050) do ( + netstat -ano | findstr ":%%p " | findstr "LISTENING" >nul + if !ERRORLEVEL! neq 0 ( + set UI_PORT=%%p + goto :ui_port_found + ) +) +:ui_port_found + +echo [OK] Server port: !SERVER_PORT! +echo [OK] UI port: !UI_PORT! + +echo. +echo [PREFLIGHT 5/7] Final Checks... + +if not exist "packages\ui\dist\index.html" ( + echo [WARN] UI build directory not found + echo [INFO] Running UI build... + pushd packages\ui + call npm run build + if !ERRORLEVEL! neq 0 ( + echo [ERROR] UI build failed! + popd + set /a ERRORS+=1 + goto :launch_check + ) + popd + echo [OK] UI build completed + set /a AUTO_FIXED+=1 +) + +if not exist "packages\electron-app\dist\main\main.js" ( + echo [WARN] Electron build incomplete + echo [INFO] Running full build... + call npm run build + if !ERRORLEVEL! neq 0 ( + echo [ERROR] Full build failed! + set /a ERRORS+=1 + goto :launch_check + ) + echo [OK] Full build completed + set /a AUTO_FIXED+=1 +) + +echo. +echo [PREFLIGHT 6/7] Launch Summary + +echo [STATUS] +echo. +echo Node.js: %NODE_VERSION% +echo npm: %NPM_VERSION% +if !BINARY_FREE_MODE! equ 1 ( + echo Mode: Binary-Free Mode +) else ( + echo Mode: Full Mode with OpenCode +) +echo Auto-fixes applied: !AUTO_FIXED! +echo Warnings: !WARNINGS! +echo Errors: !ERRORS! +echo Server Port: !SERVER_PORT! +echo UI Port: !UI_PORT! +echo. + +if !ERRORS! gtr 0 ( + echo [RESULT] Cannot start due to errors! + pause + exit /b 1 +) + +echo. +echo [PREFLIGHT 7/7] Starting NomadArch in Development Mode... +echo [INFO] Server: http://localhost:!SERVER_PORT! +echo [INFO] UI: http://localhost:!UI_PORT! +echo. + +start "NomadArch Server" cmd /k "cd /d \"%~dp0packages\server\" && set CLI_PORT=!SERVER_PORT! && npm run dev" +timeout /t 3 /nobreak >nul +start "NomadArch UI" cmd /k "cd /d \"%~dp0packages\ui\" && set VITE_PORT=!UI_PORT! && npm run dev -- --port !UI_PORT!" +timeout /t 3 /nobreak >nul +start "NomadArch Electron" cmd /k "cd /d \"%~dp0packages\electron-app\" && npm run dev" + +echo. +echo [OK] All services started. +echo Press any key to stop all services... +pause >nul + +taskkill /F /FI "WINDOWTITLE eq NomadArch*" >nul 2>&1 +taskkill /F /FI "WINDOWTITLE eq NomadArch Server*" >nul 2>&1 +taskkill /F /FI "WINDOWTITLE eq NomadArch UI*" >nul 2>&1 +taskkill /F /FI "WINDOWTITLE eq NomadArch Electron*" >nul 2>&1 + +:launch_check +pause +exit /b !ERRORS! diff --git a/Launch-Unix-Prod.sh b/Launch-Unix-Prod.sh new file mode 100644 index 0000000..7bd3201 --- /dev/null +++ b/Launch-Unix-Prod.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +# NomadArch Production Launcher for macOS and Linux +# Version: 0.4.0 + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "" +echo "NomadArch Launcher (macOS/Linux, Production Mode)" +echo "Version: 0.4.0" +echo "Features: SMART FIX / APEX / SHIELD / MULTIX MODE" +echo "" + +echo "[STEP 1/3] Checking Dependencies..." + +if ! command -v node &> /dev/null; then + echo -e "${RED}[ERROR]${NC} Node.js not found!" + echo "Please run the installer first:" + if [[ "$OSTYPE" == "darwin"* ]]; then + echo " ./Install-Mac.sh" + else + echo " ./Install-Linux.sh" + fi + exit 1 +fi + +NODE_VERSION=$(node --version) +echo -e "${GREEN}[OK]${NC} Node.js: $NODE_VERSION" + +echo "" +echo "[STEP 2/3] Checking Pre-Built UI..." + +if [[ -d "packages/electron-app/dist/renderer/assets" ]]; then + echo -e "${GREEN}[OK]${NC} Pre-built UI assets found" +else + echo -e "${RED}[ERROR]${NC} Pre-built UI assets not found." + echo "Run: npm run build" + exit 1 +fi + +echo "" +echo "[STEP 3/3] Starting NomadArch (Production Mode)..." + +cd packages/electron-app +npx electron . +EXIT_CODE=$? + +if [[ $EXIT_CODE -ne 0 ]]; then + echo "" + echo -e "${RED}[ERROR]${NC} NomadArch exited with an error!" +fi + +exit $EXIT_CODE diff --git a/Launch-Unix.sh b/Launch-Unix.sh new file mode 100644 index 0000000..0bd0af2 --- /dev/null +++ b/Launch-Unix.sh @@ -0,0 +1,195 @@ +#!/bin/bash + +# NomadArch Launcher for macOS and Linux +# Version: 0.5.0 - Binary-Free Mode + +set -euo pipefail + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +ERRORS=0 +WARNINGS=0 +AUTO_FIXED=0 +BINARY_FREE_MODE=0 + +echo "" +echo "NomadArch Launcher (macOS/Linux)" +echo "Version: 0.5.0 - Binary-Free Mode" +echo "" + +echo "[PREFLIGHT 1/7] Checking Dependencies..." + +if ! command -v node &> /dev/null; then + echo -e "${YELLOW}[WARN]${NC} Node.js not found. Running installer..." + if [[ "$OSTYPE" == "darwin"* ]]; then + bash "$SCRIPT_DIR/Install-Mac.sh" + else + bash "$SCRIPT_DIR/Install-Linux.sh" + fi + echo -e "${BLUE}[INFO]${NC} If Node.js was installed, open a new terminal and run Launch-Unix.sh again." + exit 1 +fi + +NODE_VERSION=$(node --version) +echo -e "${GREEN}[OK]${NC} Node.js: $NODE_VERSION" + +if ! command -v npm &> /dev/null; then + echo -e "${RED}[ERROR]${NC} npm not found!" + exit 1 +fi + +NPM_VERSION=$(npm --version) +echo -e "${GREEN}[OK]${NC} npm: $NPM_VERSION" + +echo "" +echo "[PREFLIGHT 2/7] Checking for OpenCode CLI (Optional)..." + +if command -v opencode &> /dev/null; then + echo -e "${GREEN}[OK]${NC} OpenCode CLI available in PATH - Full Mode" +elif [[ -f "$SCRIPT_DIR/bin/opencode" ]]; then + echo -e "${GREEN}[OK]${NC} OpenCode binary found in bin/ - Full Mode" +else + echo -e "${BLUE}[INFO]${NC} OpenCode CLI not found - Using Binary-Free Mode" + echo -e "${BLUE}[INFO]${NC} Free models (GPT-5 Nano, Grok Code, GLM-4.7) available via OpenCode Zen" + BINARY_FREE_MODE=1 +fi + +echo "" +echo "[PREFLIGHT 3/7] Checking Dependencies..." + +if [[ ! -d "node_modules" ]]; then + echo -e "${YELLOW}[INFO]${NC} Dependencies not installed. Installing now..." + if ! npm install; then + echo -e "${RED}[ERROR]${NC} Dependency installation failed!" + exit 1 + fi + echo -e "${GREEN}[OK]${NC} Dependencies installed (auto-fix)" + ((AUTO_FIXED++)) +else + echo -e "${GREEN}[OK]${NC} Dependencies found" +fi + +echo "" +echo "[PREFLIGHT 4/7] Finding Available Port..." + +DEFAULT_SERVER_PORT=3001 +DEFAULT_UI_PORT=3000 +SERVER_PORT=$DEFAULT_SERVER_PORT +UI_PORT=$DEFAULT_UI_PORT + +for port in {3001..3050}; do + # Try lsof first, then ss, then netstat + if command -v lsof &> /dev/null; then + if ! lsof -i :$port -sTCP:LISTEN -t > /dev/null 2>&1; then + SERVER_PORT=$port + break + fi + elif command -v ss &> /dev/null; then + if ! ss -tuln | grep -q ":$port "; then + SERVER_PORT=$port + break + fi + elif command -v netstat &> /dev/null; then + if ! netstat -tuln | grep -q ":$port "; then + SERVER_PORT=$port + break + fi + else + # No port checking tools, just use default + SERVER_PORT=$port + break + fi +done + +echo -e "${GREEN}[OK]${NC} Server port: $SERVER_PORT" + +echo "" +echo "[PREFLIGHT 5/7] Final Checks..." + +if [[ ! -d "packages/ui/dist" ]]; then + echo -e "${YELLOW}[WARN]${NC} UI build directory not found" + echo -e "${YELLOW}[INFO]${NC} Running UI build..." + pushd packages/ui >/dev/null + if ! npm run build; then + echo -e "${RED}[ERROR]${NC} UI build failed!" + popd >/dev/null + ((ERRORS++)) + else + popd >/dev/null + echo -e "${GREEN}[OK]${NC} UI build completed (auto-fix)" + ((AUTO_FIXED++)) + fi +else + echo -e "${GREEN}[OK]${NC} UI build directory exists" +fi + +if [[ ! -f "packages/electron-app/dist/main/main.js" ]]; then + echo -e "${YELLOW}[WARN]${NC} Electron build incomplete" + echo -e "${YELLOW}[INFO]${NC} Running full build..." + if ! npm run build; then + echo -e "${RED}[ERROR]${NC} Full build failed!" + ((ERRORS++)) + else + echo -e "${GREEN}[OK]${NC} Full build completed (auto-fix)" + ((AUTO_FIXED++)) + fi +else + echo -e "${GREEN}[OK]${NC} Electron build exists" +fi + +echo "" +echo "[PREFLIGHT 6/7] Launch Summary" + +echo -e "${BLUE}[STATUS]${NC}" +echo "" +echo " Node.js: $NODE_VERSION" +echo " npm: $NPM_VERSION" +if [[ $BINARY_FREE_MODE -eq 1 ]]; then + echo " Mode: Binary-Free Mode (No OpenCode binary required)" + echo " Free Models: GPT-5 Nano, Grok Code, GLM-4.7, Doubao, Big Pickle" +else + echo " Mode: Full Mode (OpenCode binary available)" +fi +echo " Auto-fixes applied: $AUTO_FIXED" +echo " Warnings: $WARNINGS" +echo " Errors: $ERRORS" +echo " Server Port: $SERVER_PORT" +echo "" + +if [[ $ERRORS -gt 0 ]]; then + echo -e "${RED}[RESULT]${NC} Cannot start due to errors!" + exit 1 +fi + +echo -e "${GREEN}[INFO]${NC} Starting NomadArch..." +echo -e "${GREEN}[INFO]${NC} Server will run on http://localhost:$SERVER_PORT" +echo -e "${YELLOW}[INFO]${NC} Press Ctrl+C to stop" +echo "" + +SERVER_URL="http://localhost:$SERVER_PORT" + +if [[ "$OSTYPE" == "darwin"* ]]; then + open "$SERVER_URL" 2>/dev/null || true +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + xdg-open "$SERVER_URL" 2>/dev/null || true +fi + +export CLI_PORT=$SERVER_PORT +export NOMADARCH_BINARY_FREE_MODE=$BINARY_FREE_MODE +npm run dev:electron + +EXIT_CODE=$? + +if [[ $EXIT_CODE -ne 0 ]]; then + echo "" + echo -e "${RED}[ERROR]${NC} NomadArch exited with an error!" +fi + +exit $EXIT_CODE diff --git a/Launch-Windows-Prod.bat b/Launch-Windows-Prod.bat new file mode 100644 index 0000000..d40cbf9 --- /dev/null +++ b/Launch-Windows-Prod.bat @@ -0,0 +1,57 @@ +@echo off +setlocal enabledelayedexpansion + +title NomadArch Launcher (Production Mode) +color 0A + +echo. +echo NomadArch Launcher (Windows, Production Mode) +echo Version: 0.4.0 +echo Features: SMART FIX / APEX / SHIELD / MULTIX MODE +echo. + +set SCRIPT_DIR=%~dp0 +set SCRIPT_DIR=%SCRIPT_DIR:~0,-1% +cd /d "%SCRIPT_DIR%" + +echo [STEP 1/3] Checking Dependencies... + +where node >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [WARN] Node.js not found. Running installer... + call "%SCRIPT_DIR%\Install-Windows.bat" + echo [INFO] If Node.js was installed, open a new terminal and run Launch-Windows-Prod.bat again. + pause + exit /b 1 +) + +for /f "tokens=*" %%i in ('node --version') do set NODE_VERSION=%%i +echo [OK] Node.js: %NODE_VERSION% + +echo. +echo [STEP 2/3] Checking Pre-Built UI... + +if exist "packages\electron-app\dist\renderer\assets" ( + echo [OK] Pre-built UI assets found +) else ( + echo [ERROR] Pre-built UI assets not found. + echo Run: npm run build + pause + exit /b 1 +) + +echo. +echo [STEP 3/3] Starting NomadArch (Production Mode)... + +pushd packages\electron-app +npx electron . +popd + +if %ERRORLEVEL% neq 0 ( + echo. + echo [ERROR] NomadArch exited with an error! + echo. +) + +pause +exit /b %ERRORLEVEL% diff --git a/Launch-Windows.bat b/Launch-Windows.bat new file mode 100644 index 0000000..e18a813 --- /dev/null +++ b/Launch-Windows.bat @@ -0,0 +1,215 @@ +@echo off +setlocal enabledelayedexpansion + +title NomadArch Launcher +color 0A + +echo. +echo NomadArch Launcher (Windows) +echo Version: 0.5.0 - Binary-Free Mode +echo. + +set SCRIPT_DIR=%~dp0 +set SCRIPT_DIR=%SCRIPT_DIR:~0,-1% +cd /d "%SCRIPT_DIR%" + +set ERRORS=0 +set WARNINGS=0 +set AUTO_FIXED=0 +set BINARY_FREE_MODE=0 + +echo [PREFLIGHT 1/7] Checking Dependencies... + +where node >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [WARN] Node.js not found. Running installer... + call "%SCRIPT_DIR%\Install-Windows.bat" + echo [INFO] If Node.js was installed, open a new terminal and run Launch-Windows.bat again. + pause + exit /b 1 +) + +for /f "tokens=*" %%i in ('node --version') do set NODE_VERSION=%%i +echo [OK] Node.js: %NODE_VERSION% + +where npm >nul 2>&1 +if %ERRORLEVEL% neq 0 ( + echo [ERROR] npm not found! + pause + exit /b 1 +) + +for /f "tokens=*" %%i in ('npm --version') do set NPM_VERSION=%%i +echo [OK] npm: %NPM_VERSION% + +echo. +echo [PREFLIGHT 2/7] Checking OpenCode CLI... + +where opencode >nul 2>&1 +if %ERRORLEVEL% equ 0 ( + echo [OK] OpenCode CLI in PATH - Full Mode + goto :opencode_check_done +) + +if exist "bin\opencode.exe" ( + echo [OK] OpenCode binary in bin/ - Full Mode + goto :opencode_check_done +) + +echo [INFO] OpenCode CLI not found - Using Binary-Free Mode +echo [INFO] Free models: GPT-5 Nano, Grok Code, GLM-4.7 via OpenCode Zen +set BINARY_FREE_MODE=1 + +:opencode_check_done + +echo. +echo [PREFLIGHT 3/7] Checking Dependencies... + +if not exist "node_modules" ( + echo [INFO] Dependencies not installed. Installing now... + call npm install + if !ERRORLEVEL! neq 0 ( + echo [ERROR] Dependency installation failed! + pause + exit /b 1 + ) + echo [OK] Dependencies installed + set /a AUTO_FIXED+=1 +) else ( + echo [OK] Dependencies found +) + +echo. +echo [PREFLIGHT 4/7] Finding Available Port... + +set DEFAULT_SERVER_PORT=3001 +set DEFAULT_UI_PORT=3000 +set SERVER_PORT=%DEFAULT_SERVER_PORT% +set UI_PORT=%DEFAULT_UI_PORT% + +for /l %%p in (%DEFAULT_SERVER_PORT%,1,3050) do ( + netstat -ano | findstr ":%%p " | findstr "LISTENING" >nul + if !ERRORLEVEL! neq 0 ( + set SERVER_PORT=%%p + goto :server_port_found + ) +) +:server_port_found + +echo [OK] Server port: !SERVER_PORT! + +if !SERVER_PORT! neq %DEFAULT_SERVER_PORT% ( + echo [INFO] Port %DEFAULT_SERVER_PORT% was in use, using !SERVER_PORT! instead + set /a WARNINGS+=1 +) + +echo. +echo [PREFLIGHT 5/7] Final Checks... + +if not exist "packages\ui\dist\index.html" ( + echo [WARN] UI build directory not found + echo [INFO] Running UI build... + pushd packages\ui + call npm run build + if !ERRORLEVEL! neq 0 ( + echo [ERROR] UI build failed! + popd + set /a ERRORS+=1 + goto :final_launch_check + ) + popd + echo [OK] UI build completed + set /a AUTO_FIXED+=1 +) else ( + echo [OK] UI build directory exists +) + +if not exist "packages\electron-app\dist\main\main.js" ( + echo [WARN] Electron build incomplete + echo [INFO] Running full build... + call npm run build + if !ERRORLEVEL! neq 0 ( + echo [ERROR] Full build failed! + set /a ERRORS+=1 + goto :final_launch_check + ) + echo [OK] Full build completed + set /a AUTO_FIXED+=1 +) + +echo. +echo [PREFLIGHT 6/7] Launch Summary + +echo [STATUS] +echo. +echo Node.js: %NODE_VERSION% +echo npm: %NPM_VERSION% +if !BINARY_FREE_MODE! equ 1 ( + echo Mode: Binary-Free Mode + echo Free Models: GPT-5 Nano, Grok Code, GLM-4.7, Doubao, Big Pickle +) else ( + echo Mode: Full Mode with OpenCode +) +echo Auto-fixes applied: !AUTO_FIXED! +echo Warnings: !WARNINGS! +echo Errors: !ERRORS! +echo Server Port: !SERVER_PORT! +echo. + +if !ERRORS! gtr 0 ( + echo [RESULT] Cannot start due to errors! + echo. + echo Please fix the errors above and try again. + pause + exit /b 1 +) + +echo [INFO] Starting NomadArch... +echo [INFO] Server will run on http://localhost:!SERVER_PORT! +echo [INFO] UI will run on http://localhost:!UI_PORT! +echo [INFO] Press Ctrl+C to stop +echo. + +set SERVER_URL=http://localhost:!SERVER_PORT! +set VITE_PORT=!UI_PORT! + +echo. +echo ======================================== +echo Starting UI dev server on port !UI_PORT!... +echo ======================================== + +pushd packages\ui +start "NomadArch UI Server" cmd /c "set VITE_PORT=!UI_PORT! && npm run dev" +popd + +echo [INFO] Waiting for UI dev server to start... +timeout /t 3 /nobreak >nul + +echo. +echo ======================================== +echo Starting Electron app... +echo ======================================== + +set "VITE_DEV_SERVER_URL=http://localhost:!UI_PORT!" +set "NOMADARCH_OPEN_DEVTOOLS=false" +set "NOMADARCH_BINARY_FREE_MODE=!BINARY_FREE_MODE!" +call npm run dev:electron + +if !ERRORLEVEL! neq 0 ( + echo. + echo [ERROR] NomadArch exited with an error! + echo. + echo Error Code: !ERRORLEVEL! + echo. + echo Troubleshooting: + echo 1. Ensure port !SERVER_PORT! is not in use + echo 2. Run Install-Windows.bat again + echo 3. Check log file: packages\electron-app\.log + echo. +) + +:final_launch_check +echo. +echo Press any key to exit... +pause >nul +exit /b !ERRORS! diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 0000000..cce670b --- /dev/null +++ b/PROGRESS.md @@ -0,0 +1,149 @@ +# CodeNomad - Development Progress + +## Completed Tasks + +### Task 001: Project Setup ✅ +- Set up Electron + SolidJS + Vite + TypeScript +- Configured TailwindCSS v3 (downgraded from v4 for electron-vite compatibility) +- Build pipeline with electron-vite +- Application window management +- Application menu with keyboard shortcuts + +### Task 002: Empty State UI & Folder Selection ✅ +- Empty state component with styled UI +- Native folder picker integration +- IPC handlers for folder selection +- UI state management with SolidJS signals +- Loading states with spinner +- Keyboard shortcuts (Cmd/Ctrl+N) + +### Task 003: Process Manager ✅ +- Process spawning: `opencode serve --port 0` +- Port detection from stdout (regex: `opencode server listening on http://...`) +- Process lifecycle management (spawn, kill, cleanup) +- IPC communication for instance management +- Instance state tracking (starting → ready → stopped/error) +- Auto-cleanup on app quit +- Error handling & timeout protection (10s) +- Graceful shutdown (SIGTERM → SIGKILL) + +### Task 004: SDK Integration ✅ +- Installed `@opencode-ai/sdk` package +- SDK manager for client lifecycle +- Session fetching from OpenCode server +- Agent fetching (`client.app.agents()`) +- Provider fetching (`client.config.providers()`) +- Session store with SolidJS signals +- Instance store updated with SDK client +- Loading states for async operations +- Error handling for network failures + +### Task 005: Session Picker Modal ✅ +- Modal dialog with Kobalte Dialog +- Lists ALL existing sessions (scrollable) +- Session metadata display (title, relative timestamp) +- Native HTML select dropdown for agents +- Auto-selects first agent by default +- Create new session with selected agent +- Cancel button stops instance and closes modal +- Resume session on click +- Empty state for no sessions +- Loading state for agents +- Keyboard navigation (Escape to cancel) + +## Current State + +**Working Features:** +- ✅ App launches with empty state +- ✅ Folder selection via native dialog +- ✅ OpenCode server spawning per folder +- ✅ Port extraction and process tracking +- ✅ SDK client connection to running servers +- ✅ Session list fetching and display +- ✅ Agent and provider data fetching +- ✅ Session picker modal on instance creation +- ✅ Resume existing sessions +- ✅ Create new sessions with agent selection + +**File Structure:** +``` +packages/opencode-client/ +├── electron/ +│ ├── main/ +│ │ ├── main.ts (window + IPC setup) +│ │ ├── menu.ts (app menu) +│ │ ├── ipc.ts (instance IPC handlers) +│ │ └── process-manager.ts (server spawning) +│ └── preload/ +│ └── index.ts (IPC bridge) +├── src/ +│ ├── components/ +│ │ ├── empty-state.tsx +│ │ └── session-picker.tsx +│ ├── lib/ +│ │ └── sdk-manager.ts +│ ├── stores/ +│ │ ├── ui.ts +│ │ ├── instances.ts +│ │ └── sessions.ts +│ ├── types/ +│ │ ├── electron.d.ts +│ │ ├── instance.ts +│ │ └── session.ts +│ └── App.tsx +├── tasks/ +│ ├── done/ (001-005) +│ └── todo/ (006+) +└── docs/ +``` + +## Next Steps + +### Task 006: Message Stream UI (NEXT) +- Message display component +- User/assistant message rendering +- Markdown support with syntax highlighting +- Tool use visualization +- Auto-scroll behavior + +### Task 007: Prompt Input +- Text input with multi-line support +- Send button +- File attachment support +- Keyboard shortcuts (Enter for new line; Cmd+Enter/Ctrl+Enter to send) + +### Task 008: Instance Tabs +- Tab bar for multiple instances +- Switch between instances +- Close instance tabs +- "+" button for new instance + +## Build & Test + +```bash +cd packages/opencode-client +bun run build +bunx electron . +``` + +**Known Issue:** +- Dev mode (`bun dev`) fails due to Bun workspace hoisting + electron-vite +- Workaround: Use production builds for testing + +## Dependencies + +- Electron 38 +- SolidJS 1.8 +- TailwindCSS 3.x +- @opencode-ai/sdk +- @kobalte/core (Dialog) +- Vite 5 +- TypeScript 5 + +## Stats + +- **Tasks completed:** 5/5 (Phase 1) +- **Files created:** 18+ +- **Lines of code:** ~1500+ +- **Build time:** ~7s +- **Bundle size:** 152KB (renderer) diff --git a/Prepare-Public-Release.bat b/Prepare-Public-Release.bat new file mode 100644 index 0000000..83c9e66 --- /dev/null +++ b/Prepare-Public-Release.bat @@ -0,0 +1,152 @@ +@echo off +setlocal enabledelayedexpansion + +:: ===================================================== +:: NomadArch - Clean Copy Script for Public Release +:: Creates a sanitized copy without sensitive data +:: ===================================================== + +title NomadArch Clean Copy for GitHub + +echo. +echo ===================================================== +echo NomadArch - Prepare Clean Public Release +echo ===================================================== +echo. + +set SCRIPT_DIR=%~dp0 +set SCRIPT_DIR=%SCRIPT_DIR:~0,-1% +set DEST_DIR=%USERPROFILE%\Desktop\NomadArch-Public-Release + +echo [INFO] Source: %SCRIPT_DIR% +echo [INFO] Destination: %DEST_DIR% +echo. + +if exist "%DEST_DIR%" ( + echo [WARN] Destination exists. Removing old copy... + rmdir /s /q "%DEST_DIR%" +) + +echo [STEP 1/6] Creating destination directory... +mkdir "%DEST_DIR%" + +echo [STEP 2/6] Copying core project files... + +:: Copy essential files +copy "%SCRIPT_DIR%\package.json" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\package-lock.json" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\.gitignore" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\README.md" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\BUILD.md" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\AGENTS.md" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\PROGRESS.md" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\manual_test_guide.md" "%DEST_DIR%\" >nul + +:: Copy launchers and installers +copy "%SCRIPT_DIR%\Install-*.bat" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\Install-*.sh" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\Launch-*.bat" "%DEST_DIR%\" >nul +copy "%SCRIPT_DIR%\Launch-*.sh" "%DEST_DIR%\" >nul + +echo [STEP 3/6] Copying packages directory (source only)... + +:: Use robocopy to exclude unwanted items +robocopy "%SCRIPT_DIR%\packages" "%DEST_DIR%\packages" /E /NFL /NDL /NJH /NJS /NC /NS ^ + /XD node_modules dist out release .vite .electron-vite _backup_original __pycache__ ^ + /XF *.log *.bak *.tmp *.map + +echo [STEP 4/6] Copying additional directories... + +:: Copy docs if exists +if exist "%SCRIPT_DIR%\docs" ( + robocopy "%SCRIPT_DIR%\docs" "%DEST_DIR%\docs" /E /NFL /NDL /NJH /NJS /NC /NS /XD node_modules +) + +:: Copy images if exists +if exist "%SCRIPT_DIR%\images" ( + robocopy "%SCRIPT_DIR%\images" "%DEST_DIR%\images" /E /NFL /NDL /NJH /NJS /NC /NS +) + +:: Copy dev-docs if exists +if exist "%SCRIPT_DIR%\dev-docs" ( + robocopy "%SCRIPT_DIR%\dev-docs" "%DEST_DIR%\dev-docs" /E /NFL /NDL /NJH /NJS /NC /NS +) + +:: Copy scripts directory if exists +if exist "%SCRIPT_DIR%\scripts" ( + robocopy "%SCRIPT_DIR%\scripts" "%DEST_DIR%\scripts" /E /NFL /NDL /NJH /NJS /NC /NS +) + +:: Copy .github directory (workflows, templates) +if exist "%SCRIPT_DIR%\.github" ( + robocopy "%SCRIPT_DIR%\.github" "%DEST_DIR%\.github" /E /NFL /NDL /NJH /NJS /NC /NS +) + +echo [STEP 5/6] Creating clean MCP config template... + +:: Create a template .mcp.json with placeholders +( +echo { +echo "mcpServers": { +echo "sequential-thinking": { +echo "command": "npx", +echo "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"] +echo }, +echo "desktop-commander": { +echo "command": "npx", +echo "args": ["-y", "@modelcontextprotocol/server-desktop-commander"] +echo }, +echo "web-reader": { +echo "command": "npx", +echo "args": ["-y", "@modelcontextprotocol/server-web-reader"] +echo }, +echo "github": { +echo "command": "npx", +echo "args": ["-y", "@modelcontextprotocol/server-github"], +echo "env": { +echo "GITHUB_TOKEN": "YOUR_GITHUB_TOKEN_HERE" +echo } +echo } +echo } +echo } +) > "%DEST_DIR%\.mcp.json.example" + +echo [STEP 6/6] Final cleanup... + +:: Remove any accidentally copied sensitive files +if exist "%DEST_DIR%\.opencode" rmdir /s /q "%DEST_DIR%\.opencode" +if exist "%DEST_DIR%\.trae" rmdir /s /q "%DEST_DIR%\.trae" +if exist "%DEST_DIR%\.backup" rmdir /s /q "%DEST_DIR%\.backup" +if exist "%DEST_DIR%\.tmp-qwen-code" rmdir /s /q "%DEST_DIR%\.tmp-qwen-code" +if exist "%DEST_DIR%\.agent" rmdir /s /q "%DEST_DIR%\.agent" +if exist "%DEST_DIR%\install.log" del "%DEST_DIR%\install.log" + +:: Delete any .bak files that got through +for /r "%DEST_DIR%" %%f in (*.bak) do del "%%f" 2>nul +for /r "%DEST_DIR%" %%f in (*.log) do del "%%f" 2>nul +for /r "%DEST_DIR%" %%f in (*.tmp) do del "%%f" 2>nul + +:: Remove _backup_original directories +for /d /r "%DEST_DIR%" %%d in (_backup_original) do ( + if exist "%%d" rmdir /s /q "%%d" +) + +echo. +echo ===================================================== +echo Clean Copy Complete! +echo ===================================================== +echo. +echo Location: %DEST_DIR% +echo. +echo Next Steps: +echo 1. Review the contents of %DEST_DIR% +echo 2. cd %DEST_DIR% +echo 3. git init +echo 4. git add . +echo 5. git commit -m "Initial public release" +echo 6. git remote add origin https://github.com/YOUR_USER/NomadArch.git +echo 7. git push -u origin main +echo. +echo ===================================================== + +pause diff --git a/README.md b/README.md new file mode 100644 index 0000000..561630e --- /dev/null +++ b/README.md @@ -0,0 +1,354 @@ +

+ NomadArch Logo +

+ +

🏛️ NomadArch

+ +

Advanced AI Coding Workspace

+ +

+ NomadArch is an enhanced fork of CodeNomad — now with GLM 4.7, multi-model support, and MULTIX Mode +

+ +

+ + GitHub Stars + + + GitHub Forks + + + License + +

+ +

+ Features • + AI Models • + Installation • + Usage • + What's New • + Credits +

+ +

+ + Star this repo + +

+ +--- + +## 🎯 Overview + +**NomadArch** is an enhanced fork of CodeNomad, featuring significant UI/UX improvements, additional AI integrations, and a more robust architecture. This is a full-featured AI coding assistant with support for multiple AI providers including **GLM 4.7**, Anthropic, OpenAI, Google, Qwen, and local models via Ollama. + +### ✨ Key Improvements Over CodeNomad +- 🔧 Fixed Qwen OAuth authentication flow +- 🚀 Enhanced MULTIX Mode with live token streaming +- 🎨 Improved UI/UX with detailed tooltips +- ✅ Auto-build verification on launch +- 📦 Comprehensive installer scripts for all platforms +- 🔌 Port conflict detection and resolution hints +- 🆓 **NEW: Binary-Free Mode** - No external binaries required! + +### 🆓 Binary-Free Mode (v0.5.0) + +NomadArch now works **without requiring the OpenCode binary**! This means: + +| Benefit | Description | +|---------|-------------| +| ⚡ **Faster Setup** | No binary downloads, just npm install | +| 🌍 **Universal** | Works on all platforms without platform-specific binaries | +| 🆓 **Free Models** | Access free AI models without any binary | +| 🔄 **Seamless** | Automatically uses native mode when binary unavailable | + +**Free Models Available (No API Key Required):** +- 🧠 **GPT-5 Nano** - 400K context, reasoning + tools +- ⚡ **Grok Code Fast 1** - 256K context, optimized for code +- 🌟 **GLM-4.7** - 205K context, top-tier performance +- 🚀 **Doubao Seed Code** - 256K context, specialized for coding +- 🥒 **Big Pickle** - 200K context, efficient and fast + +--- + +## 🤖 Supported AI Models + +NomadArch supports a wide range of AI models from multiple providers, giving you flexibility to choose the best model for your coding tasks. + +### 🔥 Featured Model: GLM 4.7 (Z.AI) + +**GLM 4.7** is the latest state-of-the-art open model from Z.AI, now fully integrated into NomadArch. Released in December 2025, GLM 4.7 ranks **#1 for Web Development** and **#6 overall** on the LM Arena leaderboard. + +| Feature | Description | +|---------|-------------| +| 📊 **128K Context Window** | Process entire codebases in a single session | +| 🧠 **Interleaved Thinking** | Advanced reasoning with multi-step analysis | +| 💭 **Preserved Thinking** | Maintains reasoning chain across long conversations | +| 🔄 **Turn-level Thinking** | Optimized per-response reasoning for efficiency | + +#### Benchmark Performance + +| Benchmark | Score | Notes | +|-----------|-------|-------| +| SWE-bench | **+73.8%** | Over GLM-4.6 | +| SWE-bench Multilingual | **+66.7%** | Over GLM-4.6 | +| Terminal Bench 2.0 | **+41%** | Over GLM-4.6 | +| LM Arena WebDev | **#1** | Open Model Ranking | +| LM Arena Overall | **#6** | Open Model Ranking | + +> 🎯 **Get 10% discount on Z.AI with code: [`R0K78RJKNW`](https://z.ai/subscribe?ic=R0K78RJKNW)** + +--- + +### 📋 All Supported Models + +
+🌟 Z.AI Models + +| Model | Context | Specialty | +|-------|---------|-----------| +| **GLM 4.7** | 128K | Web Development, Coding | +| GLM 4.6 | 128K | General Coding | +| GLM-4 | 128K | Versatile | + +
+ +
+🟣 Anthropic Models + +| Model | Context | Specialty | +|-------|---------|-----------| +| Claude 3.7 Sonnet | 200K | Complex Reasoning | +| Claude 3.5 Sonnet | 200K | Balanced Performance | +| Claude 3 Opus | 200K | Maximum Quality | + +
+ +
+🟢 OpenAI Models + +| Model | Context | Specialty | +|-------|---------|-----------| +| GPT-5 Preview | 200K | Latest Capabilities | +| GPT-4.1 | 128K | Production Ready | +| GPT-4 Turbo | 128K | Fast & Efficient | + +
+ +
+🔵 Google Models + +| Model | Context | Specialty | +|-------|---------|-----------| +| Gemini 2.0 Pro | 1M+ | Massive Context | +| Gemini 2.0 Flash | 1M+ | Ultra Fast | + +
+ +
+🟠 Qwen & Local Models + +| Model | Context/Size | Specialty | +|-------|--------------|-----------| +| Qwen 2.5 Coder | 32K | Code Specialized | +| Qwen 2.5 | 32K | General Purpose | +| DeepSeek Coder (Ollama) | Varies | Code | +| Llama 3.1 (Ollama) | Varies | General | + +
+ +--- + +## 📦 Installation + +### Quick Start (Recommended) + +#### Windows +```batch +Install-Windows.bat +Launch-Windows.bat +``` + +#### Linux +```bash +chmod +x Install-Linux.sh && ./Install-Linux.sh +./Launch-Unix.sh +``` + +#### macOS +```bash +chmod +x Install-Mac.sh && ./Install-Mac.sh +./Launch-Unix.sh +``` + +### Manual Installation + +```bash +git clone https://github.com/roman-ryzenadvanced/NomadArch-v1.0.git +cd NomadArch +npm install +npm run dev:electron +``` + +--- + +## 🚀 Features + +### Core Features +| Feature | Description | +|---------|-------------| +| 🤖 **Multi-Provider AI** | GLM 4.7, Anthropic, OpenAI, Google, Qwen, Ollama | +| 🖥️ **Electron Desktop App** | Native feel with modern web technologies | +| 📁 **Workspace Management** | Organize your projects efficiently | +| 💬 **Real-time Streaming** | Live responses from AI models | +| 🔧 **Smart Fix** | AI-powered code error detection and fixes | +| 🔌 **Ollama Integration** | Run local AI models for privacy | + +### UI/UX Highlights +| Mode | Description | +|------|-------------| +| ⚡ **MULTIX Mode** | Multi-task parallel AI conversations with live token counting | +| 🛡️ **SHIELD Mode** | Auto-approval for hands-free operation | +| 🚀 **APEX Mode** | Autonomous AI that chains tasks together | + +--- + +## 🆕 What's New + +
+🎨 Branding & Identity + +- ✅ **New Branding**: "NomadArch" with proper attribution to OpenCode +- ✅ **Updated Loading Screen**: New branding with fork attribution +- ✅ **Updated Empty States**: All screens show NomadArch branding + +
+ +
+🔐 Qwen OAuth Integration + +- ✅ **Fixed OAuth Flow**: Resolved "Body cannot be empty" error +- ✅ **Proper API Bodies**: POST requests now include proper JSON bodies +- ✅ **Fixed Device Poll Schema**: Corrected Fastify schema validation + +
+ +
+🚀 MULTIX Mode Enhancements + +- ✅ **Live Streaming Token Counter**: Visible in header during AI processing +- ✅ **Thinking Roller Indicator**: Animated indicator with bouncing dots +- ✅ **Token Stats Display**: Shows input/output tokens processed +- ✅ **Auto-Scroll**: Intelligent scrolling during streaming + +
+ +
+🐛 Bug Fixes + +- ✅ Fixed Qwen OAuth "empty body" errors +- ✅ Fixed MultiX panel being pushed off screen +- ✅ Fixed top menu/toolbar disappearing +- ✅ Fixed layout breaking when scrolling +- ✅ Fixed sessions not showing on workspace entry + +
+ +--- + +## 🎮 Button Guide + +| Button | Description | +|--------|-------------| +| **AUTHED** | Shows authentication status (Green = connected) | +| **AI MODEL** | Click to switch between AI models | +| **SMART FIX** | AI analyzes code for errors and applies fixes | +| **BUILD** | Compiles and builds your project | +| **APEX** | Autonomous mode - AI chains actions automatically | +| **SHIELD** | Auto-approval mode - AI makes changes without prompts | +| **MULTIX MODE** | Opens multi-task pipeline for parallel conversations | + +--- + +## 📁 Project Structure + +``` +NomadArch/ +├── Install-*.bat/.sh # Platform installers +├── Launch-*.bat/.sh # Platform launchers +├── packages/ +│ ├── electron-app/ # Electron main process +│ ├── server/ # Backend (Fastify) +│ ├── ui/ # Frontend (SolidJS + Vite) +│ └── opencode-config/ # OpenCode configuration +└── README.md +``` + +--- + +## 🔧 Requirements + +| Requirement | Version | +|-------------|---------| +| Node.js | v18+ | +| npm | v9+ | +| OS | Windows 10+, macOS 11+, Linux | + +--- + +## 🆘 Troubleshooting + +
+Common Issues & Solutions + +**Dependencies not installed?** +```bash +# Run the installer for your platform +Install-Windows.bat # Windows +./Install-Linux.sh # Linux +./Install-Mac.sh # macOS +``` + +**Port conflict?** +```bash +# Kill process on port 3000/3001 +taskkill /F /PID # Windows +kill -9 # Unix +``` + +**OAuth fails?** +1. Check internet connection +2. Complete OAuth in browser +3. Clear browser cookies and retry + +
+ +--- + +## 🙏 Credits + +Built with amazing open source projects: + +| Category | Projects | +|----------|----------| +| **Framework** | SolidJS, Vite, TypeScript, Electron | +| **UI** | TailwindCSS, Kobalte, SUID Material | +| **Backend** | Fastify, Ollama | +| **AI** | OpenCode CLI, Various AI SDKs | + +--- + +## 📄 License + +This project is a fork of [CodeNomad](https://github.com/opencode/codenom). + +--- + +

+ Made with ❤️ by NeuralNomadsAI +

+ +

+ NomadArch is an enhanced fork of CodeNomad +

diff --git a/UX Upgrade/.env.local b/UX Upgrade/.env.local new file mode 100644 index 0000000..be86fc1 --- /dev/null +++ b/UX Upgrade/.env.local @@ -0,0 +1 @@ +GEMINI_API_KEY=PLACEHOLDER_API_KEY diff --git a/bin/opencode.exe b/bin/opencode.exe new file mode 100644 index 0000000..4c59474 Binary files /dev/null and b/bin/opencode.exe differ diff --git a/dev-docs/INDEX.md b/dev-docs/INDEX.md new file mode 100644 index 0000000..290f7f7 --- /dev/null +++ b/dev-docs/INDEX.md @@ -0,0 +1,180 @@ +# Documentation Index + +Quick reference to all documentation files. + +## Main Documents + +### [README.md](../README.md) + +Project overview, installation, and getting started guide. + +### [SUMMARY.md](SUMMARY.md) + +Executive summary of the entire project - **start here!** + +### [MVP-PRINCIPLES.md](MVP-PRINCIPLES.md) + +**MVP development philosophy** - Focus on functionality, NOT performance ⚡ + +--- + +## Specification Documents + +### [architecture.md](architecture.md) + +**Complete system architecture** + +- Component layers and responsibilities +- State management structure +- Data flow diagrams +- Technology stack +- Security and performance considerations + +**Read this to understand:** How the app is structured + +### [user-interface.md](user-interface.md) + +**Complete UI/UX specifications** + +- Every screen and component layout +- Visual design specifications +- Interaction patterns +- Accessibility requirements +- Color schemes and typography + +**Read this to understand:** What the app looks like and how users interact + +### [technical-implementation.md](technical-implementation.md) + +**Implementation details** + +- File structure +- TypeScript interfaces +- Process management logic +- SDK integration patterns +- IPC communication +- Error handling strategies + +**Read this to understand:** How to actually build it + +### [build-roadmap.md](build-roadmap.md) + +**Development plan** + +- 8 phases of development +- Task dependencies +- Timeline estimates +- Success criteria +- Risk mitigation + +**Read this to understand:** The development journey from start to finish + +--- + +## Task Documents + +### [tasks/README.md](../tasks/README.md) + +**Task management guide** + +- Task workflow +- Naming conventions +- How to work on tasks +- Progress tracking + +### Task Files (in tasks/todo/) + +- **001-project-setup.md** - Electron + SolidJS boilerplate +- **002-empty-state-ui.md** - Initial UI with folder selection +- **003-process-manager.md** - OpenCode server spawning +- **004-sdk-integration.md** - API client integration +- **005-session-picker-modal.md** - Session selection UI + +More tasks will be added as we progress through phases. + +--- + +## Reading Order + +### For First-Time Readers: + +1. [SUMMARY.md](SUMMARY.md) - Get the big picture +2. [architecture.md](architecture.md) - Understand the structure +3. [user-interface.md](user-interface.md) - See what you're building +4. [build-roadmap.md](build-roadmap.md) - Understand the plan +5. [tasks/README.md](../tasks/README.md) - Learn the workflow + +### For Implementers: + +1. [tasks/README.md](../tasks/README.md) - Understand task workflow +2. [technical-implementation.md](technical-implementation.md) - Implementation patterns +3. [tasks/todo/001-\*.md](../tasks/todo/) - Start with first task +4. Refer to architecture.md and user-interface.md as needed + +### For Designers: + +1. [user-interface.md](user-interface.md) - Complete UI specs +2. [architecture.md](architecture.md) - Component structure +3. [SUMMARY.md](SUMMARY.md) - Feature overview + +### For Project Managers: + +1. [SUMMARY.md](SUMMARY.md) - Executive overview +2. [build-roadmap.md](build-roadmap.md) - Timeline and phases +3. [tasks/README.md](../tasks/README.md) - Task tracking + +--- + +## Quick Reference + +### Common Questions + +**Q: Where do I start?** +A: Read [SUMMARY.md](SUMMARY.md), then start [Task 001](../tasks/todo/001-project-setup.md) + +**Q: How long will this take?** +A: See [build-roadmap.md](build-roadmap.md) - MVP in 3-7 weeks depending on commitment + +**Q: What does the UI look like?** +A: See [user-interface.md](user-interface.md) for complete specifications + +**Q: How does it work internally?** +A: See [architecture.md](architecture.md) for system design + +**Q: How do I build feature X?** +A: See [technical-implementation.md](technical-implementation.md) for patterns + +**Q: What's the development plan?** +A: See [build-roadmap.md](build-roadmap.md) for phases + +--- + +## Document Status + +| Document | Status | Last Updated | +| --------------------------- | ----------- | ------------ | +| README.md | ✅ Complete | 2024-10-22 | +| SUMMARY.md | ✅ Complete | 2024-10-22 | +| architecture.md | ✅ Complete | 2024-10-22 | +| user-interface.md | ✅ Complete | 2024-10-22 | +| technical-implementation.md | ✅ Complete | 2024-10-22 | +| build-roadmap.md | ✅ Complete | 2024-10-22 | +| tasks/README.md | ✅ Complete | 2024-10-22 | +| Task 001-005 | ✅ Complete | 2024-10-22 | + +**Project phase:** Post-MVP (Phases 1-3 complete; Phase 4 work underway). + +--- + +## Contributing to Documentation + +When updating documentation: + +1. Update the relevant file +2. Update "Last Updated" in this index +3. Update SUMMARY.md if adding major changes +4. Keep consistent formatting and style + +--- + +_This index will be updated as more documentation is added._ diff --git a/dev-docs/MVP-PRINCIPLES.md b/dev-docs/MVP-PRINCIPLES.md new file mode 100644 index 0000000..f16579c --- /dev/null +++ b/dev-docs/MVP-PRINCIPLES.md @@ -0,0 +1,326 @@ +# MVP Development Principles + +## Core Philosophy + +**Focus on functionality, NOT performance.** + +The MVP (Minimum Viable Product) is about proving the concept and getting feedback. Performance optimization comes later, after we validate the product with real users. + +--- + +## What We Care About in MVP + +### ✅ DO Focus On: + +1. **Functionality** + - Does it work? + - Can users complete their tasks? + - Are all core features present? + +2. **Correctness** + - Does it produce correct results? + - Does error handling work? + - Is data persisted properly? + +3. **User Experience** + - Is the UI intuitive? + - Are loading states clear? + - Are error messages helpful? + +4. **Stability** + - Does it crash? + - Can users recover from errors? + - Does it lose data? + +5. **Code Quality** + - Is code readable? + - Are types correct? + - Is it maintainable? + +### ❌ DON'T Focus On: + +1. **Performance Optimization** + - Virtual scrolling + - Message batching + - Lazy loading + - Memory optimization + - Render optimization + +2. **Scalability** + - Handling 1000+ messages + - Multiple instances with 100+ sessions + - Large file attachments + - Massive search indexes + +3. **Advanced Features** + - Plugins + - Advanced search + - Custom themes + - Workspace management + +--- + +## Specific MVP Guidelines + +### Messages & Rendering + +**Simple approach:** + +```typescript +// Just render everything - no virtual scrolling + + {(message) => } + +``` + +**Don't worry about:** + +- Sessions with 500+ messages +- Re-render performance +- Memory usage +- Scroll performance + +**When to optimize:** + +- Post-MVP (Phase 8) +- Only if users report issues +- Based on real-world usage data + +### State Management + +**Simple approach:** + +- Use SolidJS signals directly +- No batching +- No debouncing +- No caching layers + +**Don't worry about:** + +- Update frequency +- Number of reactive dependencies +- State structure optimization + +### Process Management + +**Simple approach:** + +- Spawn servers as needed +- Kill on close +- Basic error handling + +**Don't worry about:** + +- Resource limits (max processes) +- CPU/memory monitoring +- Restart optimization +- Process pooling + +### API Communication + +**Simple approach:** + +- Direct SDK calls +- Basic error handling +- Simple retry (if at all) + +**Don't worry about:** + +- Request batching +- Response caching +- Optimistic updates +- Request deduplication + +--- + +## Decision Framework + +When implementing any feature, ask: + +### Is this optimization needed for MVP? + +**NO if:** + +- It only helps with large datasets +- It only helps with many instances +- It's about speed, not correctness +- Users won't notice the difference +- It adds significant complexity + +**YES if:** + +- Users can't complete basic tasks without it +- App is completely unusable without it +- It prevents data loss +- It's a security requirement + +### Examples + +**Virtual Scrolling:** ❌ NO for MVP + +- MVP users won't have 1000+ message sessions +- Simple list rendering works fine for <100 messages +- Add in Phase 8 if needed + +**Error Handling:** ✅ YES for MVP + +- Users need clear feedback when things fail +- Prevents frustration and data loss +- Core to usability + +**Message Batching:** ❌ NO for MVP + +- SolidJS handles updates efficiently +- Only matters at very high frequency +- Add later if users report lag + +**Session Persistence:** ✅ YES for MVP + +- Users expect sessions to persist +- Losing work is unacceptable +- Core functionality + +--- + +## Testing Approach + +### MVP Testing Focus + +**Test for:** + +- ✅ Correctness (does it work?) +- ✅ Error handling (does it fail gracefully?) +- ✅ Data integrity (is data saved?) +- ✅ User flows (can users complete tasks?) + +**Don't test for:** + +- ❌ Performance benchmarks +- ❌ Load testing +- ❌ Stress testing +- ❌ Scalability limits + +### Acceptable Performance + +For MVP, these are **acceptable:** + +- 100 messages render in 1 second +- UI slightly laggy during heavy streaming +- Memory usage grows with message count +- Multiple instances slow down app + +These become **unacceptable** only if: + +- Users complain +- App becomes unusable +- Basic tasks can't be completed + +--- + +## When to Optimize + +### Post-MVP Triggers + +Add optimization when: + +1. **User Feedback** + - Multiple users report slowness + - Users abandon due to performance + - Performance prevents usage + +2. **Measurable Issues** + - App freezes for >2 seconds + - Memory usage causes crashes + - UI becomes unresponsive + +3. **Phase 8 Reached** + - MVP complete and validated + - User base established + - Performance becomes focus + +### How to Optimize + +When the time comes: + +1. **Measure First** + - Profile actual bottlenecks + - Use real user data + - Identify specific problems + +2. **Target Fixes** + - Fix the specific bottleneck + - Don't over-engineer + - Measure improvement + +3. **Iterate** + - Optimize one thing at a time + - Verify with users + - Stop when "fast enough" + +--- + +## Communication with Users + +### During Alpha/Beta + +**Be honest about performance:** + +- "This is an MVP - expect some slowness with large sessions" +- "We're focused on functionality first" +- "Performance optimization is planned for v1.x" + +**Set expectations:** + +- Works best with <200 messages per session +- Multiple instances may slow things down +- We'll optimize based on your feedback + +### Collecting Feedback + +**Ask about:** + +- ✅ What features are missing? +- ✅ What's confusing? +- ✅ What doesn't work? +- ✅ Is it too slow to use? + +**Don't ask about:** + +- ❌ How many milliseconds for X? +- ❌ Memory usage specifics +- ❌ Benchmark comparisons + +--- + +## Summary + +### The MVP Mantra + +> **Make it work, then make it better, then make it fast.** + +For CodeNomad MVP: + +- **Phase 1-7:** Make it work, make it better +- **Phase 8+:** Make it fast + +### Remember + +- Premature optimization is the root of all evil +- Real users provide better optimization guidance than assumptions +- Functionality > Performance for MVP +- You can't optimize what users don't use + +--- + +## Quick Reference + +**When in doubt, ask:** + +1. Is this feature essential for users to do their job? → Build it +2. Is this optimization essential for the feature to work? → Build it +3. Is this just making it faster/more efficient? → Defer to Phase 8 + +**MVP = Minimum _Viable_ Product** + +- Viable = works and is useful +- Viable ≠ optimized and fast diff --git a/dev-docs/SUMMARY.md b/dev-docs/SUMMARY.md new file mode 100644 index 0000000..253270a --- /dev/null +++ b/dev-docs/SUMMARY.md @@ -0,0 +1,348 @@ +# CodeNomad - Project Summary + +## Current Status + +We have completed the MVP milestones (Phases 1-3) and are now operating in post-MVP mode. Future work prioritizes multi-instance support, advanced input polish, and system integrations outlined in later phases. + +## What We've Created + +A comprehensive specification and task breakdown for building the CodeNomad desktop application. + +## Directory Structure + +``` +packages/opencode-client/ +├── docs/ # Comprehensive documentation +│ ├── architecture.md # System architecture & design +│ ├── user-interface.md # UI/UX specifications +│ ├── technical-implementation.md # Technical details & patterns +│ ├── build-roadmap.md # Phased development plan +│ └── SUMMARY.md # This file +├── tasks/ +│ ├── README.md # Task management guide +│ ├── todo/ # Tasks to implement +│ │ ├── 001-project-setup.md +│ │ ├── 002-empty-state-ui.md +│ │ ├── 003-process-manager.md +│ │ ├── 004-sdk-integration.md +│ │ └── 005-session-picker-modal.md +│ └── done/ # Completed tasks (empty) +└── README.md # Project overview + +``` + +## Documentation Overview + +### 1. Architecture (architecture.md) + +**What it covers:** + +- High-level system design +- Component layers (Main process, Renderer, Communication) +- State management approach +- Tab hierarchy (Instance tabs → Session tabs) +- Data flow for key operations +- Technology stack decisions +- Security considerations + +**Key sections:** + +- Component architecture diagram +- Instance/Session state structures +- Communication patterns (HTTP, SSE) +- Error handling strategies +- Performance considerations + +### 2. User Interface (user-interface.md) + +**What it covers:** + +- Complete UI layout specifications +- Visual design for every component +- Interaction patterns +- Keyboard shortcuts +- Accessibility requirements +- Empty states and error states +- Modal designs + +**Key sections:** + +- Detailed layout wireframes (ASCII art) +- Component-by-component specifications +- Message rendering formats +- Control bar designs +- Modal/overlay specifications +- Color schemes and typography + +### 3. Technical Implementation (technical-implementation.md) + +**What it covers:** + +- Technology stack details +- Project file structure +- State management patterns +- Process management implementation +- SDK integration approach +- SSE event handling +- IPC communication +- Error handling strategies +- Performance optimizations + +**Key sections:** + +- Complete project structure +- TypeScript interfaces +- Process spawning logic +- SDK client management +- Message rendering implementation +- Build and packaging config + +### 4. Build Roadmap (build-roadmap.md) + +**What it covers:** + +- 8 development phases +- Task dependencies +- Timeline estimates +- Success criteria per phase +- Risk mitigation +- Release strategy + +**Phases:** + +1. **Foundation** (Week 1) - Project setup, process management +2. **Core Chat** (Week 2) - Message display, SSE streaming +3. **Essential Features** (Week 3) - Markdown, agents, errors +4. **Multi-Instance** (Week 4) - Multiple projects support +5. **Advanced Input** (Week 5) - Commands, file attachments +6. **Polish** (Week 6) - UX refinements, settings +7. **System Integration** (Week 7) - Native features +8. **Advanced** (Week 8+) - Performance, plugins + +## Task Breakdown + +### Current Tasks (Phase 1) + +**001 - Project Setup** (2-3 hours) + +- Set up Electron + SolidJS + Vite +- Configure TypeScript, TailwindCSS +- Create basic project structure +- Verify build pipeline works + +**002 - Empty State UI** (2-3 hours) + +- Create empty state component +- Implement folder selection dialog +- Add keyboard shortcuts +- Style and test responsiveness + +**003 - Process Manager** (4-5 hours) + +- Spawn OpenCode server processes +- Parse stdout for port extraction +- Kill processes on command +- Handle errors and timeouts +- Auto-cleanup on app quit + +**004 - SDK Integration** (3-4 hours) + +- Create SDK client per instance +- Fetch sessions, agents, models +- Implement session CRUD operations +- Add error handling and retries + +**005 - Session Picker Modal** (3-4 hours) + +- Build modal with session list +- Agent selector for new sessions +- Keyboard navigation +- Loading and error states + +**Total Phase 1 time: ~15-20 hours (2-3 weeks part-time)** + +## Key Design Decisions + +### 1. Two-Level Tabs + +- **Level 1**: Instance tabs (one per project folder) +- **Level 2**: Session tabs (multiple per instance) +- Allows working on multiple projects with multiple conversations each + +### 2. Process Management in Main Process + +- Electron main process spawns servers +- Parses stdout to get port +- IPC sends port to renderer +- Ensures clean shutdown on app quit + +### 3. One SDK Client Per Instance + +- Each instance has its own HTTP client +- Connects to different port (different server) +- Isolated state prevents cross-contamination + +### 4. SolidJS for Reactivity + +- Fine-grained reactivity for SSE updates +- No re-render cascades +- Better performance for real-time updates +- Smaller bundle size than React + +### 5. No Virtual Scrolling or Performance Optimization in MVP + +- Start with simple list rendering +- Don't optimize for large sessions initially +- Focus on functionality, not performance +- Add optimizations in post-MVP phases if needed +- Reduces initial complexity and speeds up development + +### 6. Messages and Tool Calls Inline + +- All activity shows in main message stream +- Tool calls expandable/collapsible +- File changes visible inline +- Single timeline view + +## Implementation Guidelines + +### For Each Task: + +1. Read task file completely +2. Review related documentation +3. Follow steps in order +4. Check off acceptance criteria +5. Test thoroughly +6. Move to done/ when complete + +### Code Standards: + +- TypeScript for everything +- No `any` types +- Descriptive variable names +- Comments for complex logic +- Error handling on all async operations +- Loading states for all network calls + +### Testing Approach: + +- Manual testing at each step +- Test on minimum window size (800x600) +- Test error cases +- Test edge cases (long text, special chars) +- Keyboard navigation verification + +## Next Steps + +### To Start Building: + +1. **Read all documentation** + - Understand architecture + - Review UI specifications + - Study technical approach + +2. **Start with Task 001** + - Set up project structure + - Install dependencies + - Verify build works + +3. **Follow sequential order** + - Each task builds on previous + - Don't skip ahead + - Dependencies matter + +4. **Track progress** + - Update task checkboxes + - Move completed tasks to done/ + - Update roadmap as you go + +### When You Hit Issues: + +1. Review task prerequisites +2. Check documentation for clarification +3. Look at related specs +4. Ask questions on unclear requirements +5. Document blockers and solutions + +## Success Metrics + +### MVP (After Task 015) + +- Can select folder → spawn server → chat +- Messages stream in real-time +- Can switch agents and models +- Tool executions visible +- Basic error handling works +- **Performance is NOT a concern** - focus on functionality + +### Beta (After Task 030) + +- Multi-instance support +- Advanced input (files, commands) +- Polished UX +- Settings and preferences +- Native menus + +### v1.0 (After Task 035) + +- System tray integration +- Auto-updates +- Crash reporting +- Production-ready stability + +## Useful References + +### Within This Project: + +- `README.md` - Project overview and getting started +- `docs/architecture.md` - System design +- `docs/user-interface.md` - UI specifications +- `docs/technical-implementation.md` - Implementation details +- `tasks/README.md` - Task workflow guide + +### External: + +- OpenCode server API: https://opencode.ai/docs/server/ +- Electron docs: https://electronjs.org/docs +- SolidJS docs: https://solidjs.com +- Kobalte UI: https://kobalte.dev + +## Questions to Resolve + +Before starting implementation, clarify: + +1. Exact OpenCode CLI syntax for spawning server +2. Expected stdout format for port extraction +3. SDK package location and version +4. Any platform-specific gotchas +5. Icon and branding assets location + +## Estimated Timeline + +**Conservative estimate (part-time, ~15 hours/week):** + +- Phase 1 (MVP Foundation): 2-3 weeks +- Phase 2 (Core Chat): 2 weeks +- Phase 3 (Essential): 2 weeks +- **MVP Complete: 6-7 weeks** + +**Aggressive estimate (full-time, ~40 hours/week):** + +- Phase 1: 1 week +- Phase 2: 1 week +- Phase 3: 1 week +- **MVP Complete: 3 weeks** + +Add 2-4 weeks for testing, bug fixes, and polish before alpha release. + +## This is a Living Document + +As you build: + +- Update estimates based on actual time +- Add new tasks as needed +- Refine specifications +- Document learnings +- Track blockers and solutions + +Good luck! 🚀 diff --git a/dev-docs/TOOL_CALL_IMPLEMENTATION.md b/dev-docs/TOOL_CALL_IMPLEMENTATION.md new file mode 100644 index 0000000..507159b --- /dev/null +++ b/dev-docs/TOOL_CALL_IMPLEMENTATION.md @@ -0,0 +1,228 @@ +# Tool Call Rendering Implementation + +This document describes how tool calls are rendered in the CodeNomad, following the patterns established in the TUI. + +## Overview + +Each tool type has specialized rendering logic that displays the most relevant information for that tool. This matches the TUI's approach of providing context-specific displays rather than generic input/output dumps. + +## Tool-Specific Rendering + +### 1. **read** - File Reading + +- **Title**: `Read {filename}` +- **Body**: Preview of file content (first 6 lines) from `metadata.preview` +- **Use case**: Shows what file content the assistant is reading + +### 2. **edit** - File Editing + +- **Title**: `Edit {filename}` +- **Body**: Diff/patch showing changes from `metadata.diff` +- **Special**: Shows diagnostics if available in metadata +- **Use case**: Shows what changes are being made to files + +### 3. **write** - File Writing + +- **Title**: `Write {filename}` +- **Body**: File content being written (first 10 lines) +- **Special**: Shows diagnostics if available in metadata +- **Use case**: Shows new file content being created + +### 4. **bash** - Shell Commands + +- **Title**: `Shell {description}` (or command if no description) +- **Body**: Console-style display with `$ command` and output + +``` +$ npm install vitest +added 50 packages... +``` + +- **Output from**: `metadata.output` +- **Use case**: Shows command execution and results + +### 5. **webfetch** - Web Fetching + +- **Title**: `Fetch {url}` +- **Body**: Fetched content (first 10 lines) +- **Use case**: Shows web content being retrieved + +### 6. **todowrite** - Task Planning + +- **Title**: Dynamic based on todo phase: + - All pending: "Creating plan" + - All completed: "Completing plan" + - Mixed: "Updating plan" +- **Body**: Formatted todo list: + - `- [x] Completed task` + - `- [ ] Pending task` + - `- [ ] ~~Cancelled task~~` + - `- [ ] In progress task` (highlighted) +- **Use case**: Shows the AI's task planning + +### 7. **task** - Delegated Tasks + +- **Title**: `Task[subagent_type] {description}` +- **Body**: List of delegated tool calls with icons: + +``` +⚡ bash: npm install +📖 read package.json +✏️ edit src/app.ts +``` + +- **Special**: In TUI, includes navigation hints for session tree +- **Use case**: Shows what the delegated agent is doing + +### 8. **todoread** - Plan Reading + +- **Special**: Hidden in TUI, returns empty string +- **Use case**: Internal tool, not displayed to user + +### 9. **glob** - File Pattern Matching + +- **Title**: `Glob {pattern}` +- **Use case**: Shows file search patterns + +### 10. **grep** - Content Search + +- **Title**: `Grep "{pattern}"` +- **Use case**: Shows what content is being searched + +### 11. **list** - Directory Listing + +- **Title**: `List` +- **Use case**: Shows directory operations + +### 12. **patch** - Patching Files + +- **Title**: `Patch` +- **Use case**: Shows patch operations + +### 13. **invalid** - Invalid Tool Calls + +- **Title**: Name of the actual tool attempted +- **Use case**: Shows validation errors + +### 14. **Default** - Unknown Tools + +- **Title**: Capitalized tool name +- **Body**: Output truncated to 10 lines +- **Use case**: Fallback for any new or custom tools + +## Status States + +### Pending + +- **Icon**: ⏸ (pause symbol) +- **Title**: Action text (e.g., "Writing command...", "Preparing edit...") +- **Border**: Accent color +- **Animation**: Shimmer effect on title +- **Expandable**: Shows "Waiting for permission..." message + +### Running + +- **Icon**: ⏳ (hourglass) +- **Title**: Same as completed state +- **Border**: Warning color (yellow/orange) +- **Animation**: Pulse on status icon + +### Completed + +- **Icon**: ✓ (checkmark) +- **Title**: Tool-specific title with arguments +- **Border**: Success color (green) +- **Body**: Tool-specific rendered content + +### Error + +- **Icon**: ✗ (X mark) +- **Title**: Same format but in error color +- **Border**: Error color (red) +- **Body**: Error message in highlighted box + +## Title Rendering Logic + +The title follows this pattern: + +1. **Pending state**: Show action text + + ``` + "Writing command..." + "Preparing edit..." + "Delegating..." + ``` + +2. **Completed/Running/Error**: Show specific info + + ``` + "Shell npm install" + "Edit src/app.ts" + "Read package.json" + "Task[general] Search for files" + ``` + +3. **Special cases**: + - `todowrite`: Shows plan phase + - `todoread`: Just "Plan" + - `bash`: Uses description if available, otherwise shows command + +## Metadata Usage + +Tool calls use `metadata` for rich content: + +- **read**: `metadata.preview` - file preview content +- **edit**: `metadata.diff` - patch/diff text +- **bash**: `metadata.output` - command output +- **todowrite**: `metadata.todos[]` - todo items with status +- **task**: `metadata.summary[]` - delegated tool calls +- **edit/write**: `metadata.diagnostics` - LSP diagnostics + +## Design Principles + +1. **Context-specific**: Each tool shows the most relevant information +2. **Progressive disclosure**: Collapsed by default, expand for details +3. **Visual hierarchy**: Icons, colors, and borders indicate status +4. **Truncation**: Long content is truncated (6-10 lines) to prevent overwhelming +5. **Consistency**: All tools follow same header/body/error structure + +## Component Structure + +```tsx +
+ + + {expanded && ( +
+ {/* Tool-specific body content */} + {error &&
{error}
} +
+ )} +
+``` + +## CSS Classes + +- `.tool-call` - Base container +- `.tool-call-status-{pending|running|completed|error}` - Status-specific styling +- `.tool-call-header` - Clickable header with expand/collapse +- `.tool-call-emoji` - Tool type icon +- `.tool-call-summary` - Tool title/description +- `.tool-call-details` - Expanded content area +- `.tool-call-content` - Code/output content (monospace) +- `.tool-call-todos` - Todo list container +- `.tool-call-task-summary` - Delegated task list +- `.tool-call-error-content` - Error message display + +## Future Enhancements + +1. **Syntax highlighting**: Use Shiki for code blocks in bash, read, write +2. **Diff rendering**: Better diff visualization for edit tool +3. **Copy buttons**: Quick copy for code/output +4. **File links**: Click filename to open in editor +5. **Diagnostics display**: Show LSP errors/warnings inline diff --git a/dev-docs/architecture.md b/dev-docs/architecture.md new file mode 100644 index 0000000..21f9465 --- /dev/null +++ b/dev-docs/architecture.md @@ -0,0 +1,312 @@ +# CodeNomad Architecture + +## Overview + +CodeNomad is a cross-platform desktop application built with Electron that provides a multi-instance, multi-session interface for interacting with OpenCode servers. Each instance manages its own OpenCode server process and can handle multiple concurrent sessions. + +## High-Level Architecture + +``` +┌─────────────────────────────────────────────────────────┐ +│ Electron Main Process │ +│ - Window management │ +│ - Process spawning (opencode serve) │ +│ - IPC bridge to renderer │ +│ - File system operations │ +└────────────────┬────────────────────────────────────────┘ + │ IPC +┌────────────────┴────────────────────────────────────────┐ +│ Electron Renderer Process │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ SolidJS Application │ │ +│ │ ┌────────────────────────────────────────────┐ │ │ +│ │ │ Instance Manager │ │ │ +│ │ │ - Spawns/kills OpenCode servers │ │ │ +│ │ │ - Manages SDK clients per instance │ │ │ +│ │ │ - Handles port allocation │ │ │ +│ │ └────────────────────────────────────────────┘ │ │ +│ │ ┌────────────────────────────────────────────┐ │ │ +│ │ │ State Management (SolidJS Stores) │ │ │ +│ │ │ - instances[] │ │ │ +│ │ │ - sessions[] per instance │ │ │ +│ │ │ - normalized message store per session │ │ │ +│ │ └────────────────────────────────────────────┘ │ │ +│ │ ┌────────────────────────────────────────────┐ │ │ +│ │ │ UI Components │ │ │ +│ │ │ - InstanceTabs │ │ │ +│ │ │ - SessionTabs │ │ │ +│ │ │ - MessageSection │ │ │ +│ │ │ - PromptInput │ │ │ +│ │ └────────────────────────────────────────────┘ │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + │ HTTP/SSE +┌────────────────┴────────────────────────────────────────┐ +│ Multiple OpenCode Server Processes │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Instance 1 │ │ Instance 2 │ │ Instance 3 │ │ +│ │ Port: 4096 │ │ Port: 4097 │ │ Port: 4098 │ │ +│ │ ~/project-a │ │ ~/project-a │ │ ~/api │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +## Component Layers + +### 1. Main Process Layer (Electron) + +**Responsibilities:** + +- Create and manage application window +- Spawn OpenCode server processes as child processes +- Parse server stdout to extract port information +- Handle process lifecycle (start, stop, restart) +- Provide IPC handlers for renderer requests +- Manage native OS integrations (file dialogs, menus) + +**Key Modules:** + +- `main.ts` - Application entry point +- `process-manager.ts` - OpenCode server process spawning +- `ipc-handlers.ts` - IPC communication handlers +- `menu.ts` - Native application menu + +### 2. Renderer Process Layer (SolidJS) + +**Responsibilities:** + +- Render UI components +- Manage application state +- Handle user interactions +- Communicate with OpenCode servers via HTTP/SSE +- Real-time message streaming + +**Key Modules:** + +- `App.tsx` - Root component +- `stores/` - State management +- `components/` - UI components +- `contexts/` - SolidJS context providers +- `lib/` - Utilities and helpers + +### 3. Communication Layer + +**HTTP API Communication:** + +- SDK client per instance +- RESTful API calls for session/config/file operations +- Error handling and retries + +**SSE (Server-Sent Events):** + +- One EventSource per instance +- Real-time message updates +- Event type routing +- Reconnection logic + +**CLI Proxy Paths:** + +- The CLI server terminates all HTTP/SSE traffic and forwards it to the correct OpenCode instance. +- Each `WorkspaceDescriptor` exposes `proxyPath` (e.g., `/workspaces//instance`), which acts as the base URL for both REST and SSE calls. +- The renderer never touches the random per-instance port directly; it only talks to `window.location.origin + proxyPath` so a single CLI port can front every session. + +## Data Flow + +### Instance Creation Flow + +1. User selects folder via Electron file dialog +2. Main process receives folder path via IPC +3. Main process spawns `opencode serve --port 0` +4. Main process parses stdout for port number +5. Main process sends port + PID back to renderer +6. Renderer creates SDK client for that port +7. Renderer fetches initial session list +8. Renderer displays session picker + +### Message Streaming Flow + +1. User submits prompt in active session +2. Renderer POSTs to `/session/:id/message` +3. SSE connection receives `MessageUpdated` events +4. Events are routed to correct instance → session +5. Message state updates trigger UI re-render +6. Messages display with auto-scroll + +### Child Session Creation Flow + +1. OpenCode server creates child session +2. SSE emits `SessionUpdated` event with `parentId` +3. Renderer adds session to instance's session list +4. New session tab appears automatically +5. Optional: Auto-switch to new tab + +## State Management + +### Instance State + +``` +instances: Map + activeSessionId: string | null + logs: string[] +}> +``` + +### Session State + +``` +Session: { + id: string + title: string + parentId: string | null + messages: Message[] + agent: string + model: { providerId: string, modelId: string } + status: 'idle' | 'streaming' | 'error' +} +``` + +### Message State + +``` +Message: { + id: string + sessionId: string + type: 'user' | 'assistant' + parts: Part[] + timestamp: number + status: 'sending' | 'sent' | 'streaming' | 'complete' | 'error' +} +``` + +## Tab Hierarchy + +### Level 1: Instance Tabs + +Each tab represents one OpenCode server instance: + +- Label: Folder name (with counter if duplicate) +- Icon: Folder icon +- Close button: Stops server and closes tab +- "+" button: Opens folder picker for new instance + +### Level 2: Session Tabs + +Each instance has multiple session tabs: + +- Main session tab (always present) +- Child session tabs (auto-created) +- Logs tab (shows server output) +- "+" button: Creates new session + +### Tab Behavior + +**Instance Tab Switching:** + +- Preserves session tabs +- Switches active SDK client +- Updates SSE event routing + +**Session Tab Switching:** + +- Loads messages for that session +- Updates agent/model controls +- Preserves scroll position + +## Technology Stack + +### Core + +- **Electron** - Desktop wrapper +- **SolidJS** - Reactive UI framework +- **TypeScript** - Type safety +- **Vite** - Build tool + +### UI + +- **TailwindCSS** - Styling +- **Kobalte** - Accessible UI primitives +- **Shiki** - Code syntax highlighting +- **Marked** - Markdown parsing + +### Communication + +- **OpenCode SDK** - API client +- **EventSource** - SSE streaming +- **Node Child Process** - Process spawning + +## Error Handling + +### Process Errors + +- Server fails to start → Show error in instance tab +- Server crashes → Attempt auto-restart once +- Port already in use → Find next available port + +### Network Errors + +- API call fails → Show inline error, allow retry +- SSE disconnects → Auto-reconnect with backoff +- Timeout → Show timeout error, allow manual retry + +### User Errors + +- Invalid folder selection → Show error dialog +- Permission denied → Show actionable error message +- Out of memory → Graceful degradation message + +## Performance Considerations + +**Note: Performance optimization is NOT a focus for MVP. These are future considerations.** + +### Message Rendering (Post-MVP) + +- Start with simple list rendering - no virtual scrolling +- No message limits initially +- Only optimize if users report issues +- Virtual scrolling can be added in Phase 8 if needed + +### State Updates + +- SolidJS fine-grained reactivity handles most cases +- No special optimizations needed for MVP +- Batching/debouncing can be added later if needed + +### Memory Management (Post-MVP) + +- No memory management in MVP +- Let browser/OS handle it +- Add limits only if problems arise in testing + +## Security Considerations + +- No remote code execution +- Server spawned with user permissions +- No eval() or dangerous innerHTML +- Sanitize markdown rendering +- Validate all IPC messages +- HTTPS only for external requests + +## Extensibility Points + +### Plugin System (Future) + +- Custom slash commands +- Custom message renderers +- Theme extensions +- Keybinding customization + +### Configuration (Future) + +- Per-instance settings +- Global preferences +- Workspace-specific configs +- Import/export settings diff --git a/dev-docs/build-roadmap.md b/dev-docs/build-roadmap.md new file mode 100644 index 0000000..1fd48cb --- /dev/null +++ b/dev-docs/build-roadmap.md @@ -0,0 +1,391 @@ +# CodeNomad Build Roadmap + +## Overview + +This document outlines the phased approach to building the CodeNomad desktop application. Each phase builds incrementally on the previous, with clear deliverables and milestones. + +**Status:** MVP (Phases 1-3) is complete. Focus now shifts to post-MVP phases starting with multi-instance support and advanced input refinements. + +## MVP Scope (Phases 1-3) + +The minimum viable product includes: + +- Single instance management +- Session selection and creation +- Message display (streaming) +- Basic prompt input (text only) +- Agent/model selection +- Process lifecycle management + +**Target: 3-4 weeks for MVP** + +--- + +## Phase 1: Foundation (Week 1) + +**Goal:** Running Electron app that can spawn OpenCode servers + +### Tasks + +1. ✅ **001-project-setup** - Electron + SolidJS + Vite boilerplate +2. ✅ **002-empty-state-ui** - Empty state UI with folder selection +3. ✅ **003-process-manager** - Spawn and manage OpenCode server processes +4. ✅ **004-sdk-integration** - Connect to server via SDK +5. ✅ **005-session-picker-modal** - Select/create session modal + +### Deliverables + +- App launches successfully +- Can select folder +- Server spawns automatically +- Session picker appears +- Can create/select session + +### Success Criteria + +- User can launch app → select folder → see session picker +- Server process runs in background +- Sessions fetch from API successfully + +--- + +## Phase 2: Core Chat Interface (Week 2) + +**Goal:** Display messages and send basic prompts + +### Tasks + +6. **006-instance-session-tabs** - Two-level tab navigation +7. **007-message-display** - Render user and assistant messages +8. **008-sse-integration** - Real-time message streaming +9. **009-prompt-input-basic** - Text input with send functionality +10. **010-tool-call-rendering** - Display tool executions inline + +### Deliverables + +- Tab navigation works +- Messages display correctly +- Real-time updates via SSE +- Can send text messages +- Tool calls show status + +### Success Criteria + +- User can type message → see response stream in real-time +- Tool executions visible and expandable +- Multiple sessions can be open simultaneously + +--- + +## Phase 3: Essential Features (Week 3) + +**Goal:** Feature parity with basic TUI functionality + +### Tasks + +11. **011-agent-model-selectors** - Dropdown for agent/model switching +12. **012-markdown-rendering** - Proper markdown with code highlighting +13. **013-logs-tab** - View server logs +14. **014-error-handling** - Comprehensive error states and recovery +15. **015-keyboard-shortcuts** - Essential keyboard navigation + +### Deliverables + +- Can switch agents and models +- Markdown renders beautifully +- Code blocks have syntax highlighting +- Server logs accessible +- Errors handled gracefully +- Cmd/Ctrl+N, K, L shortcuts work + +### Success Criteria + +- User experience matches TUI quality +- All error cases handled +- Keyboard-first navigation option available + +--- + +## Phase 4: Multi-Instance Support (Week 4) + +**Goal:** Work on multiple projects simultaneously + +### Tasks + +16. **016-instance-tabs** - Instance-level tab management +17. **017-instance-state-persistence** - Remember instances across restarts +18. **018-child-session-handling** - Auto-create tabs for child sessions +19. **019-instance-lifecycle** - Stop, restart, reconnect instances +20. **020-multiple-sdk-clients** - One SDK client per instance + +### Deliverables + +- Multiple instance tabs +- Persists across app restarts +- Child sessions appear as new tabs +- Can stop individual instances +- All instances work independently + +### Success Criteria + +- User can work on 3+ projects simultaneously +- App remembers state on restart +- No interference between instances + +--- + +## Phase 5: Advanced Input (Week 5) + +**Goal:** Full input capabilities matching TUI + +### Tasks + +21. **021-slash-commands** - Command palette with autocomplete +22. **022-file-attachments** - @ mention file picker +23. **023-drag-drop-files** - Drag files onto input +24. **024-attachment-chips** - Display and manage attachments +25. **025-input-history** - Up/down arrow message history + +### Deliverables + +- `/command` autocomplete works +- `@file` picker searches files +- Drag & drop attaches files +- Attachment chips removable +- Previous messages accessible + +### Success Criteria + +- Input feature parity with TUI +- File context easy to add +- Command discovery intuitive + +--- + +## Phase 6: Polish & UX (Week 6) + +**Goal:** Production-ready user experience + +### Tasks + +26. **026-message-actions** - Copy, edit, regenerate messages +27. **027-search-in-session** - Find text in conversation +28. **028-session-management** - Rename, share, export sessions +29. **029-settings-ui** - Preferences and configuration +30. **030-native-menus** - Platform-native menu bar + +### Deliverables + +- Message context menus +- Search within conversation +- Session CRUD operations +- Settings dialog +- Native File/Edit/View menus + +### Success Criteria + +- Feels polished and professional +- All common actions accessible +- Settings discoverable + +--- + +## Phase 7: System Integration (Week 7) + +**Goal:** Native desktop app features + +### Tasks + +31. **031-system-tray** - Background running with tray icon +32. **032-notifications** - Desktop notifications for events +33. **033-auto-updater** - In-app update mechanism +34. **034-crash-reporting** - Error reporting and recovery +35. **035-performance-profiling** - Optimize rendering and memory + +### Deliverables + +- Runs in background +- Notifications for session activity +- Auto-updates on launch +- Crash logs captured +- Smooth performance with large sessions + +### Success Criteria + +- App feels native to platform +- Updates seamlessly +- Crashes don't lose data + +--- + +## Phase 8: Advanced Features (Week 8+) + +**Goal:** Beyond MVP, power user features + +### Tasks + +36. **036-virtual-scrolling** - Handle 1000+ message sessions +37. **037-message-search-advanced** - Full-text search across sessions +38. **038-workspace-management** - Save/load workspace configurations +39. **039-theme-customization** - Custom themes and UI tweaks +40. **040-plugin-system** - Extension API for custom tools + +### Deliverables + +- Virtual scrolling for performance +- Cross-session search +- Workspace persistence +- Theme editor +- Plugin loader + +### Success Criteria + +- Handles massive sessions (5000+ messages) +- Can search entire project history +- Fully customizable + +--- + +## Parallel Tracks + +Some tasks can be worked on independently: + +### Design Track + +- Visual design refinements +- Icon creation +- Brand assets +- Marketing materials + +### Documentation Track + +- User guide +- Keyboard shortcuts reference +- Troubleshooting docs +- Video tutorials + +### Infrastructure Track + +- CI/CD pipeline +- Automated testing +- Release automation +- Analytics integration + +--- + +## Release Strategy + +### Alpha (After Phase 3) + +- Internal testing only +- Frequent bugs expected +- Rapid iteration + +### Beta (After Phase 6) + +- Public beta program +- Feature complete +- Bug fixes and polish + +### v1.0 (After Phase 7) + +- Public release +- Stable and reliable +- Production-ready + +### v1.x (Phase 8+) + +- Regular feature updates +- Community-driven priorities +- Plugin ecosystem + +--- + +## Success Metrics + +### MVP Success + +- 10 internal users daily +- Can complete full coding session +- <5 critical bugs + +### Beta Success + +- 100+ external users +- NPS >50 +- <10 bugs per week + +### v1.0 Success + +- 1000+ users +- <1% crash rate +- Feature requests > bug reports + +--- + +## Risk Mitigation + +### Technical Risks + +- **Process management complexity** + - Mitigation: Extensive testing, graceful degradation +- **SSE connection stability** + - Mitigation: Robust reconnection logic, offline mode +- **Performance with large sessions** + - Mitigation: NOT a concern for MVP - defer to Phase 8 + - Accept slower performance initially, optimize later based on user feedback + +### Product Risks + +- **Feature creep** + - Mitigation: Strict MVP scope, user feedback prioritization +- **Over-optimization too early** + - Mitigation: Focus on functionality first, optimize in Phase 8 + - Avoid premature performance optimization +- **Platform inconsistencies** + - Mitigation: Test on all platforms regularly + +--- + +## Dependencies + +### External + +- OpenCode CLI availability +- OpenCode SDK stability +- Electron framework updates + +### Internal + +- Design assets +- Documentation +- Testing resources + +--- + +## Milestone Checklist + +### Pre-Alpha + +- [ ] All Phase 1 tasks complete +- [ ] Can create instance and session +- [ ] Internal demo successful + +### Alpha + +- [ ] All Phase 2-3 tasks complete +- [ ] MVP feature complete +- [ ] 5+ internal users testing + +### Beta + +- [ ] All Phase 4-6 tasks complete +- [ ] Multi-instance stable +- [ ] 50+ external testers + +### v1.0 + +- [ ] All Phase 7 tasks complete +- [ ] Documentation complete +- [ ] <5 known bugs +- [ ] Ready for public release diff --git a/dev-docs/solidjs-llms.txt b/dev-docs/solidjs-llms.txt new file mode 100644 index 0000000..0158a84 --- /dev/null +++ b/dev-docs/solidjs-llms.txt @@ -0,0 +1,82 @@ +# SolidJS Documentation + +> Solid is a modern JavaScript framework for building user interfaces with fine-grained reactivity. It compiles JSX to real DOM elements and updates only what changes, delivering exceptional performance without a virtual DOM. Solid provides reactive primitives like signals, effects, and stores for predictable state management. + +SolidJS is a declarative JavaScript framework that prioritizes performance and developer experience. Unlike frameworks that re-run components on every update, Solid components run once during initialization and set up a reactive system that precisely updates the DOM when dependencies change. + +Key principles: +- Fine-grained reactivity: Updates only the specific DOM nodes that depend on changed data +- Compile-time optimization: JSX transforms into efficient DOM operations +- Unidirectional data flow: Props are read-only, promoting predictable state management +- Component lifecycle: Components run once, with reactive primitives handling updates + +**Use your web fetch tool on any of the following links to understand the relevant concept**. + +## Quick Start + +- [Overview](https://docs.solidjs.com/): Framework introduction and key advantages +- [Quick Start](https://docs.solidjs.com/quick-start): Installation and project setup with create-solid +- [Interactive Tutorial](https://www.solidjs.com/tutorial/introduction_basics): Learn Solid basics through guided examples +- [Playground](https://playground.solidjs.com/): Experiment with Solid directly in your browser + +## Core Concepts + +- [Intro to Reactivity](https://docs.solidjs.com/concepts/intro-to-reactivity): Signals, subscribers, and reactive principles +- [Understanding JSX](https://docs.solidjs.com/concepts/understanding-jsx): How Solid uses JSX and key differences from HTML +- [Components Basics](https://docs.solidjs.com/concepts/components/basics): Component trees, lifecycles, and composition patterns +- [Signals](https://docs.solidjs.com/concepts/signals): Core reactive primitive for state management with getters/setters +- [Effects](https://docs.solidjs.com/concepts/effects): Side effects, dependency tracking, and lifecycle functions +- [Stores](https://docs.solidjs.com/concepts/stores): Complex state management with proxy-based reactivity +- [Context](https://docs.solidjs.com/concepts/context): Cross-component state sharing without prop drilling + +## Component APIs + +- [Props](https://docs.solidjs.com/concepts/components/props): Passing data and handlers to child components +- [Event Handlers](https://docs.solidjs.com/concepts/components/event-handlers): Managing user interactions +- [Class and Style](https://docs.solidjs.com/concepts/components/class-style): Dynamic styling approaches +- [Refs](https://docs.solidjs.com/concepts/refs): Accessing DOM elements directly + +## Control Flow + +- [Conditional Rendering](https://docs.solidjs.com/concepts/control-flow/conditional-rendering): Show, Switch, and Match components +- [List Rendering](https://docs.solidjs.com/concepts/control-flow/list-rendering): For, Index, and keyed iteration +- [Dynamic](https://docs.solidjs.com/concepts/control-flow/dynamic): Dynamic component switching +- [Portal](https://docs.solidjs.com/concepts/control-flow/portal): Rendering outside component hierarchy +- [Error Boundary](https://docs.solidjs.com/concepts/control-flow/error-boundary): Graceful error handling + +## Derived Values + +- [Derived Signals](https://docs.solidjs.com/concepts/derived-values/derived-signals): Computed values from signals +- [Memos](https://docs.solidjs.com/concepts/derived-values/memos): Cached computed values for performance + +## State Management + +- [Basic State Management](https://docs.solidjs.com/guides/state-management): One-way data flow and lifting state +- [Complex State Management](https://docs.solidjs.com/guides/complex-state-management): Stores for scalable applications +- [Fetching Data](https://docs.solidjs.com/guides/fetching-data): Async data with createResource + +## Routing + +- [Routing & Navigation](https://docs.solidjs.com/guides/routing-and-navigation): @solidjs/router setup and usage +- [Dynamic Routes](https://docs.solidjs.com/guides/routing-and-navigation#dynamic-routes): Route parameters and validation +- [Nested Routes](https://docs.solidjs.com/guides/routing-and-navigation#nested-routes): Hierarchical route structures +- [Preload Functions](https://docs.solidjs.com/guides/routing-and-navigation#preload-functions): Parallel data fetching + +## Advanced Topics + +- [Fine-Grained Reactivity](https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity): Deep dive into reactive system +- [TypeScript](https://docs.solidjs.com/configuration/typescript): Type safety and configuration + +## Ecosystem + +- [Solid Router](https://docs.solidjs.com/solid-router/): File-system routing and data APIs +- [SolidStart](https://docs.solidjs.com/solid-start/): Full-stack meta-framework +- [Solid Meta](https://docs.solidjs.com/solid-meta/): Document head management +- [Templates](https://github.com/solidjs/templates): Starter templates for different setups + +## Optional + +- [Ecosystem Libraries](https://www.solidjs.com/ecosystem): Community packages and tools +- [API Reference](https://docs.solidjs.com/reference/): Complete API documentation +- [Testing](https://docs.solidjs.com/guides/testing): Testing strategies and utilities +- [Deployment](https://docs.solidjs.com/guides/deploying-your-app): Build and deployment options diff --git a/dev-docs/technical-implementation.md b/dev-docs/technical-implementation.md new file mode 100644 index 0000000..5d7e6d6 --- /dev/null +++ b/dev-docs/technical-implementation.md @@ -0,0 +1,642 @@ +# Technical Implementation Details + +## Technology Stack + +### Core Technologies + +- **Electron** v28+ - Desktop application wrapper +- **SolidJS** v1.8+ - Reactive UI framework +- **TypeScript** v5.3+ - Type-safe development +- **Vite** v5+ - Fast build tool and dev server + +### UI & Styling + +- **TailwindCSS** v4+ - Utility-first styling +- **Kobalte** - Accessible UI primitives for SolidJS +- **Shiki** - Syntax highlighting for code blocks +- **Marked** - Markdown parsing +- **Lucide** - Icon library + +### Communication + +- **OpenCode SDK** (@opencode-ai/sdk) - API client +- **EventSource API** - Server-sent events +- **Node Child Process** - Process management + +### Development Tools + +- **electron-vite** - Electron + Vite integration +- **electron-builder** - Application packaging +- **ESLint** - Code linting +- **Prettier** - Code formatting + +## Project Structure + +``` +packages/opencode-client/ +├── electron/ +│ ├── main/ +│ │ ├── main.ts # Electron main entry +│ │ ├── window.ts # Window management +│ │ ├── process-manager.ts # OpenCode server spawning +│ │ ├── ipc.ts # IPC handlers +│ │ └── menu.ts # Application menu +│ ├── preload/ +│ │ └── index.ts # Preload script (IPC bridge) +│ └── resources/ +│ └── icon.png # Application icon +├── src/ +│ ├── components/ +│ │ ├── instance-tabs.tsx # Level 1 tabs +│ │ ├── session-tabs.tsx # Level 2 tabs +│ │ ├── message-stream-v2.tsx # Messages display (normalized store) +│ │ ├── message-item.tsx # Single message +│ │ ├── tool-call.tsx # Tool execution display +│ │ ├── prompt-input.tsx # Input with attachments +│ │ ├── agent-selector.tsx # Agent dropdown +│ │ ├── model-selector.tsx # Model dropdown +│ │ ├── session-picker.tsx # Startup modal +│ │ ├── logs-view.tsx # Server logs +│ │ └── empty-state.tsx # No instances view +│ ├── stores/ +│ │ ├── instances.ts # Instance state +│ │ ├── sessions.ts # Session state per instance +│ │ └── ui.ts # UI state (active tabs, etc) +│ ├── lib/ +│ │ ├── sdk-manager.ts # SDK client management +│ │ ├── sse-manager.ts # SSE connection handling +│ │ ├── port-finder.ts # Find available ports +│ │ └── markdown.ts # Markdown rendering utils +│ ├── hooks/ +│ │ ├── use-instance.ts # Instance operations +│ │ ├── use-session.ts # Session operations +│ │ └── use-messages.ts # Message operations +│ ├── types/ +│ │ ├── instance.ts # Instance types +│ │ ├── session.ts # Session types +│ │ └── message.ts # Message types +│ ├── App.tsx # Root component +│ ├── main.tsx # Renderer entry +│ └── index.css # Global styles +├── docs/ # Documentation +├── tasks/ # Task tracking +├── package.json +├── tsconfig.json +├── electron.vite.config.ts +├── tailwind.config.js +└── README.md +``` + +## State Management + +### Instance Store + +```typescript +interface InstanceState { + instances: Map + activeInstanceId: string | null + + // Actions + createInstance(folder: string): Promise + removeInstance(id: string): Promise + setActiveInstance(id: string): void +} + +interface Instance { + id: string // UUID + folder: string // Absolute path + port: number // Server port + pid: number // Process ID + status: InstanceStatus + client: OpenCodeClient // SDK client + eventSource: EventSource | null // SSE connection + sessions: Map + activeSessionId: string | null + logs: LogEntry[] +} + +type InstanceStatus = + | "starting" // Server spawning + | "ready" // Server connected + | "error" // Failed to start + | "stopped" // Server killed + +interface LogEntry { + timestamp: number + level: "info" | "error" | "warn" + message: string +} +``` + +### Session Store + +```typescript +interface SessionState { + // Per instance + getSessions(instanceId: string): Session[] + getActiveSession(instanceId: string): Session | null + + // Actions + createSession(instanceId: string, agent: string): Promise + deleteSession(instanceId: string, sessionId: string): Promise + setActiveSession(instanceId: string, sessionId: string): void + updateSession(instanceId: string, sessionId: string, updates: Partial): void +} + +interface Session { + id: string + instanceId: string + title: string + parentId: string | null + agent: string + model: { + providerId: string + modelId: string + } + version: string + time: { created: number; updated: number } + revert?: { + messageID?: string + partID?: string + snapshot?: string + diff?: string + } +} + +// Message content lives in the normalized message-v2 store +// keyed by instanceId/sessionId/messageId + +type SessionStatus = + | "idle" // No activity + | "streaming" // Assistant responding + | "error" // Error occurred + +``` + +### UI Store + +```typescript +interface UIState { + // Tab state + instanceTabOrder: string[] + sessionTabOrder: Map // instanceId -> sessionIds + + // Modal state + showSessionPicker: string | null // instanceId or null + showSettings: boolean + + // Actions + reorderInstanceTabs(newOrder: string[]): void + reorderSessionTabs(instanceId: string, newOrder: string[]): void + openSessionPicker(instanceId: string): void + closeSessionPicker(): void +} +``` + +## Process Management + +### Server Spawning + +**Strategy:** Spawn with port 0 (random), parse stdout for actual port + +```typescript +interface ProcessManager { + spawn(folder: string): Promise + kill(pid: number): Promise + restart(pid: number, folder: string): Promise +} + +interface ProcessInfo { + pid: number + port: number + stdout: Readable + stderr: Readable +} + +// Implementation approach: +// 1. Check if opencode binary exists +// 2. Spawn: spawn('opencode', ['serve', '--port', '0'], { cwd: folder }) +// 3. Listen to stdout +// 4. Parse line matching: "Server listening on port 4096" +// 5. Resolve promise with port +// 6. Timeout after 10 seconds +``` + +### Port Parsing + +```typescript +// Expected output from opencode serve: +// > Starting OpenCode server... +// > Server listening on port 4096 +// > API available at http://localhost:4096 + +function parsePort(output: string): number | null { + const match = output.match(/port (\d+)/) + return match ? parseInt(match[1], 10) : null +} +``` + +### Error Handling + +**Server fails to start:** + +- Parse stderr for error message +- Display in instance tab with retry button +- Common errors: Port in use, permission denied, binary not found + +**Server crashes after start:** + +- Detect via process 'exit' event +- Attempt auto-restart once +- If restart fails, show error state +- Preserve session data for manual restart + +## Communication Layer + +### SDK Client Management + +```typescript +interface SDKManager { + createClient(port: number): OpenCodeClient + destroyClient(port: number): void + getClient(port: number): OpenCodeClient | null +} + +// One client per instance +// Client lifecycle tied to instance lifecycle +``` + +### SSE Event Handling + +```typescript +interface SSEManager { + connect(instanceId: string, port: number): void + disconnect(instanceId: string): void + + // Event routing + onMessageUpdate(handler: (instanceId: string, event: MessageUpdateEvent) => void): void + onSessionUpdate(handler: (instanceId: string, event: SessionUpdateEvent) => void): void + onError(handler: (instanceId: string, error: Error) => void): void +} + +// Event flow: +// 1. EventSource connects to /event endpoint +// 2. Events arrive as JSON +// 3. Route to correct instance store +// 4. Update reactive state +// 5. UI auto-updates via signals +``` + +### Reconnection Logic + +```typescript +// SSE disconnects: +// - Network issue +// - Server restart +// - Tab sleep (browser optimization) + +class SSEConnection { + private reconnectAttempts = 0 + private maxReconnectAttempts = 5 + private reconnectDelay = 1000 // Start with 1s + + reconnect() { + if (this.reconnectAttempts >= this.maxReconnectAttempts) { + this.emitError(new Error("Max reconnection attempts reached")) + return + } + + setTimeout(() => { + this.connect() + this.reconnectAttempts++ + this.reconnectDelay *= 2 // Exponential backoff + }, this.reconnectDelay) + } +} +``` + +## Message Rendering + +### Markdown Processing + +```typescript +// Use Marked + Shiki for syntax highlighting +import { marked } from "marked" +import { markedHighlight } from "marked-highlight" +import { getHighlighter } from "shiki" + +const highlighter = await getHighlighter({ + themes: ["github-dark", "github-light"], + langs: ["typescript", "javascript", "python", "bash", "json"], +}) + +marked.use( + markedHighlight({ + highlight(code, lang) { + return highlighter.codeToHtml(code, { + lang, + theme: isDark ? "github-dark" : "github-light", + }) + }, + }), +) +``` + +### Tool Call Rendering + +```typescript +interface ToolCallComponent { + tool: string // "bash", "edit", "read" + input: any // Tool-specific input + output?: any // Tool-specific output + status: "pending" | "running" | "success" | "error" + expanded: boolean // Collapse state +} + +// Render logic: +// - Default: Collapsed, show summary +// - Click: Toggle expanded state +// - Running: Show spinner +// - Complete: Show checkmark +// - Error: Show error icon + message +``` + +### Streaming Updates + +```typescript +// Messages stream in via SSE +// Update strategy: Replace existing message parts + +function handleMessagePartUpdate(event: MessagePartEvent) { + const session = getSession(event.sessionId) + const message = session.messages.find((m) => m.id === event.messageId) + + if (!message) { + // New message + session.messages.push(createMessage(event)) + } else { + // Update existing + const partIndex = message.parts.findIndex((p) => p.id === event.partId) + if (partIndex === -1) { + message.parts.push(event.part) + } else { + message.parts[partIndex] = event.part + } + } + + // SolidJS reactivity triggers re-render +} +``` + +## Performance Considerations + +**MVP Approach: Don't optimize prematurely** + +### Message Rendering (MVP) + +**Simple approach - no optimization:** + +```typescript +// Render all messages - no virtual scrolling, no limits + + {(message) => } + + +// SolidJS will handle reactivity efficiently +// Only optimize if users report issues +``` + +### State Update Batching + +**Not needed for MVP:** + +- SolidJS reactivity is efficient enough +- SSE updates will just trigger normal re-renders +- Add batching only if performance issues arise + +### Memory Management + +**Not needed for MVP:** + +- No message limits +- No pruning +- No lazy loading +- Let users create as many messages as they want +- Optimize later if problems occur + +**When to add optimizations (post-MVP):** + +- Users report slowness with large sessions +- Measurable performance degradation +- Memory usage becomes problematic +- See Phase 8 tasks for virtual scrolling and optimization + +## IPC Communication + +### Main Process → Renderer + +```typescript +// Events sent from main to renderer +type MainToRenderer = { + "instance:started": { id: string; port: number; pid: number } + "instance:error": { id: string; error: string } + "instance:stopped": { id: string } + "instance:log": { id: string; entry: LogEntry } +} +``` + +### Renderer → Main Process + +```typescript +// Commands sent from renderer to main +type RendererToMain = { + "folder:select": () => Promise + "instance:create": (folder: string) => Promise<{ port: number; pid: number }> + "instance:stop": (pid: number) => Promise + "app:quit": () => void +} +``` + +### Preload Script (Bridge) + +```typescript +// Expose safe IPC methods to renderer +contextBridge.exposeInMainWorld("electronAPI", { + selectFolder: () => ipcRenderer.invoke("folder:select"), + createInstance: (folder: string) => ipcRenderer.invoke("instance:create", folder), + stopInstance: (pid: number) => ipcRenderer.invoke("instance:stop", pid), + onInstanceStarted: (callback) => ipcRenderer.on("instance:started", callback), + onInstanceError: (callback) => ipcRenderer.on("instance:error", callback), +}) +``` + +## Error Handling Strategy + +### Network Errors + +```typescript +// HTTP request fails +try { + const response = await client.session.list() +} catch (error) { + if (error.code === "ECONNREFUSED") { + // Server not responding + showError("Cannot connect to server. Is it running?") + } else if (error.code === "ETIMEDOUT") { + // Request timeout + showError("Request timed out. Retry?", { retry: true }) + } else { + // Unknown error + showError(error.message) + } +} +``` + +### SSE Errors + +```typescript +eventSource.onerror = (error) => { + // Connection lost + if (eventSource.readyState === EventSource.CLOSED) { + // Attempt reconnect + reconnectSSE() + } +} +``` + +### User Input Errors + +```typescript +// Validate before sending +function validatePrompt(text: string): string | null { + if (!text.trim()) { + return "Message cannot be empty" + } + if (text.length > 10000) { + return "Message too long (max 10000 characters)" + } + return null +} +``` + +## Security Measures + +### IPC Security + +- Use `contextIsolation: true` +- Whitelist allowed IPC channels +- Validate all data from renderer +- No `nodeIntegration` in renderer + +### Process Security + +- Spawn OpenCode with user permissions only +- No shell execution of user input +- Sanitize file paths + +### Content Security + +- Sanitize markdown before rendering +- Use DOMPurify for HTML sanitization +- No `dangerouslySetInnerHTML` without sanitization +- CSP headers in renderer + +## Testing Strategy (Future) + +### Unit Tests + +- State management logic +- Utility functions +- Message parsing + +### Integration Tests + +- Process spawning +- SDK client operations +- SSE event handling + +### E2E Tests + +- Complete user flows +- Multi-instance scenarios +- Error recovery + +## Build & Packaging + +### Development + +```bash +npm run dev # Start Electron + Vite dev server +npm run dev:main # Main process only +npm run dev:renderer # Renderer only +``` + +### Production + +```bash +npm run build # Build all +npm run build:main # Build main process +npm run build:renderer # Build renderer +npm run package # Create distributable +``` + +### Distribution + +- macOS: DMG + auto-update +- Windows: NSIS installer + auto-update +- Linux: AppImage + deb/rpm + +## Configuration Files + +### electron.vite.config.ts + +```typescript +import { defineConfig } from "electron-vite" +import solid from "vite-plugin-solid" + +export default defineConfig({ + main: { + build: { + rollupOptions: { + external: ["electron"], + }, + }, + }, + preload: { + build: { + rollupOptions: { + external: ["electron"], + }, + }, + }, + renderer: { + plugins: [solid()], + resolve: { + alias: { + "@": "/src", + }, + }, + }, +}) +``` + +### tsconfig.json + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020", "DOM"], + "jsx": "preserve", + "jsxImportSource": "solid-js", + "moduleResolution": "bundler", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "paths": { + "@/*": ["./src/*"] + } + } +} +``` diff --git a/dev-docs/user-interface.md b/dev-docs/user-interface.md new file mode 100644 index 0000000..3ea3f62 --- /dev/null +++ b/dev-docs/user-interface.md @@ -0,0 +1,493 @@ +# User Interface Specification + +## Overview + +The CodeNomad interface consists of a two-level tabbed layout with instance tabs at the top and session tabs below. Each session displays a message stream and prompt input. + +## Layout Structure + +``` +┌──────────────────────────────────────────────────────────────┐ +│ File Edit View Window Help ● ○ ◐ │ ← Native menu bar +├──────────────────────────────────────────────────────────────┤ +│ [~/project-a] [~/project-a (2)] [~/api-service] [+] │ ← Instance tabs (Level 1) +├──────────────────────────────────────────────────────────────┤ +│ [Main] [Fix login] [Write tests] [Logs] [+] │ ← Session tabs (Level 2) +├──────────────────────────────────────────────────────────────┤ +│ │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ Messages Area │ │ +│ │ │ │ +│ │ User: How do I set up testing? │ │ +│ │ │ │ +│ │ Assistant: To set up testing, you'll need to... │ │ +│ │ → bash: npm install vitest ✓ │ │ +│ │ Output: added 50 packages │ │ +│ │ │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ │ +├──────────────────────────────────────────────────────────────┤ +│ Agent: Build ▼ Model: Claude 3.5 Sonnet ▼ │ ← Controls +├──────────────────────────────────────────────────────────────┤ +│ [@file.ts] [@api.ts] [×] │ ← Attachments +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ Type your message or /command... │ │ ← Prompt input +│ │ │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ [▶] │ ← Send button +└──────────────────────────────────────────────────────────────┘ +``` + +## Components Specification + +### 1. Instance Tabs (Level 1) + +**Visual Design:** + +- Horizontal tabs at top of window +- Each tab shows folder name +- Icon: Folder icon (🗂️) +- Close button (×) on hover +- Active tab: Highlighted with accent color +- Inactive tabs: Muted background + +**Tab Label Format:** + +- Single instance: `~/project-name` +- Multiple instances of same folder: `~/project-name (2)`, `~/project-name (3)` +- Max width: 200px with ellipsis for long paths +- Tooltip shows full path on hover + +**Actions:** + +- Click: Switch to that instance +- Close (×): Stop server and close instance (with confirmation) +- Drag: Reorder tabs (future) + +**New Instance Button (+):** + +- Always visible at right end +- Click: Opens folder picker dialog +- Keyboard: Cmd/Ctrl+N + +**States:** + +- Starting: Loading spinner + "Starting..." +- Ready: Normal appearance +- Error: Red indicator + error icon +- Stopped: Grayed out (should not be visible, tab closes) + +### 2. Session Tabs (Level 2) + +**Visual Design:** + +- Horizontal tabs below instance tabs +- Smaller than instance tabs +- Each tab shows session title or "Untitled" +- Active tab: Underline or bold +- Parent-child relationship: No visual distinction (all siblings) + +**Tab Types:** + +**Session Tab:** + +- Label: Session title (editable on double-click) +- Icon: Chat bubble (💬) or none +- Close button (×) on hover +- Max width: 150px with ellipsis + +**Logs Tab:** + +- Label: "Logs" +- Icon: Terminal (⚡) +- Always present per instance +- Non-closable +- Shows server stdout/stderr + +**Actions:** + +- Click: Switch to that session +- Double-click label: Rename session +- Close (×): Delete session (with confirmation if has messages) +- Right-click: Context menu (Share, Export, Delete) + +**New Session Button (+):** + +- Click: Creates new session with default agent +- Keyboard: Cmd/Ctrl+T + +### 3. Messages Area + +**Container:** + +- Scrollable viewport +- Auto-scroll to bottom when new messages arrive +- Manual scroll up: Disable auto-scroll +- "Scroll to bottom" button appears when scrolled up + +**Message Layout:** + +**User Message:** + +``` +┌──────────────────────────────────────────┐ +│ You 10:32 AM │ +│ How do I set up testing? │ +│ │ +│ [@src/app.ts] [@package.json] │ ← Attachments if any +└──────────────────────────────────────────┘ +``` + +**Assistant Message:** + +```` +┌──────────────────────────────────────────┐ +│ Assistant • Build 10:32 AM │ +│ To set up testing, you'll need to │ +│ install Vitest and configure it. │ +│ │ +│ ▶ bash: npm install vitest ✓ │ ← Tool call (collapsed) +│ │ +│ ▶ edit src/vitest.config.ts ✓ │ +│ │ +│ Here's the configuration I added: │ +│ ```typescript │ +│ export default { │ +│ test: { globals: true } │ +│ } │ +│ ``` │ +└──────────────────────────────────────────┘ +```` + +**Tool Call (Collapsed):** + +``` +▶ bash: npm install vitest ✓ + ^ ^ ^ + | | | +Icon Tool name + summary Status +``` + +**Tool Call (Expanded):** + +``` +▼ bash: npm install vitest ✓ + + Input: + { + "command": "npm install vitest" + } + + Output: + added 50 packages, and audited 51 packages in 2s + found 0 vulnerabilities +``` + +**Status Icons:** + +- ⏳ Pending (spinner) +- ✓ Success (green checkmark) +- ✗ Error (red X) +- ⚠ Warning (yellow triangle) + +**File Change Display:** + +``` +▶ edit src/vitest.config.ts ✓ + Modified: src/vitest.config.ts + +12 lines, -3 lines +``` + +Click to expand: Show diff inline + +### 4. Controls Bar + +**Agent Selector:** + +- Dropdown button showing current agent +- Click: Opens dropdown with agent list +- Shows: Agent name + description +- Grouped by category (if applicable) + +**Model Selector:** + +- Dropdown button showing current model +- Click: Opens dropdown with model list +- Shows: Provider icon + Model name +- Grouped by provider +- Displays: Context window, capabilities icons + +**Layout:** + +``` +┌────────────────────────────────────────────┐ +│ Agent: Build ▼ Model: Claude 3.5 ▼ │ +└────────────────────────────────────────────┘ +``` + +### 5. Prompt Input + +**Input Field:** + +- Multi-line textarea +- Auto-expanding (max 10 lines) +- Placeholder: "Type your message or /command..." +- Supports keyboard shortcuts + +**Features:** + +**Slash Commands:** + +- Type `/` → Autocomplete dropdown appears +- Shows: Command name + description +- Filter as you type +- Enter to execute + +**File Mentions:** + +- Type `@` → File picker appears +- Search files by name +- Shows: File icon + path +- Enter to attach + +**Attachments:** + +- Display as chips above input +- Format: [@filename] [×] +- Click × to remove +- Drag & drop files onto input area + +**Send Button:** + +- Icon: Arrow (▶) or paper plane +- Click: Submit message +- Keyboard: Enter (without Shift) +- Disabled when: Empty input or server busy + +**Keyboard Shortcuts:** + +- Enter: New line +- Cmd+Enter (macOS) / Ctrl+Enter (Windows/Linux): Send message +- Cmd/Ctrl+K: Clear input +- Cmd/Ctrl+V: Paste (handles files) +- Cmd/Ctrl+L: Focus input +- Up/Down: Navigate message history (when input empty) + +## Overlays & Modals + +### Session Picker (Startup) + +Appears when instance starts: + +``` +┌────────────────────────────────────────┐ +│ OpenCode • ~/project-a │ +├────────────────────────────────────────┤ +│ Resume a session: │ +│ │ +│ > Fix login bug 2h ago │ +│ Add dark mode 5h ago │ +│ Refactor API Yesterday │ +│ │ +│ ────────────── or ────────────── │ +│ │ +│ Start new session: │ +│ Agent: [Build ▼] [Start] │ +│ │ +│ [Cancel] │ +└────────────────────────────────────────┘ +``` + +**Actions:** + +- Click session: Resume that session +- Click "Start": Create new session with selected agent +- Click "Cancel": Close instance +- Keyboard: Arrow keys to navigate, Enter to select + +### Confirmation Dialogs + +**Close Instance:** + +``` +┌────────────────────────────────────────┐ +│ Stop OpenCode instance? │ +├────────────────────────────────────────┤ +│ This will stop the server for: │ +│ ~/project-a │ +│ │ +│ Active sessions will be lost. │ +│ │ +│ [Cancel] [Stop Instance] │ +└────────────────────────────────────────┘ +``` + +**Delete Session:** + +``` +┌────────────────────────────────────────┐ +│ Delete session? │ +├────────────────────────────────────────┤ +│ This will permanently delete: │ +│ "Fix login bug" │ +│ │ +│ This cannot be undone. │ +│ │ +│ [Cancel] [Delete] │ +└────────────────────────────────────────┘ +``` + +## Empty States + +### No Instances + +``` +┌──────────────────────────────────────────┐ +│ │ +│ [Folder Icon] │ +│ │ +│ Start Coding with AI │ +│ │ +│ Select a folder to start coding with AI │ +│ │ +│ [Select Folder] │ +│ │ +│ Keyboard shortcut: Cmd/Ctrl+N │ +│ │ +└──────────────────────────────────────────┘ +``` + +### No Messages (New Session) + +``` +┌──────────────────────────────────────────┐ +│ │ +│ Start a conversation │ +│ │ +│ Type a message below or try: │ +│ • /init-project │ +│ • Ask about your codebase │ +│ • Attach files with @ │ +│ │ +└──────────────────────────────────────────┘ +``` + +### Logs Tab (No Logs Yet) + +``` +┌──────────────────────────────────────────┐ +│ Waiting for server output... │ +└──────────────────────────────────────────┘ +``` + +## Visual Styling + +### Color Scheme + +**Light Mode:** + +- Background: #FFFFFF +- Secondary background: #F5F5F5 +- Border: #E0E0E0 +- Text: #1A1A1A +- Muted text: #666666 +- Accent: #0066FF + +**Dark Mode:** + +- Background: #1A1A1A +- Secondary background: #2A2A2A +- Border: #3A3A3A +- Text: #E0E0E0 +- Muted text: #999999 +- Accent: #0080FF + +### Typography + +- **Main text**: 14px, system font +- **Headers**: 16px, medium weight +- **Labels**: 12px, regular weight +- **Code**: Monospace font (Consolas, Monaco, Courier) +- **Line height**: 1.5 + +### Spacing + +- **Padding**: 8px, 12px, 16px, 24px (consistent scale) +- **Margins**: Same as padding +- **Tab height**: 40px +- **Input height**: 80px (auto-expanding) +- **Message spacing**: 16px between messages + +### Icons + +- Use consistent icon set (Lucide, Heroicons, or similar) +- Size: 16px for inline, 20px for buttons +- Stroke width: 2px + +## Responsive Behavior + +### Minimum Window Size + +- Width: 800px +- Height: 600px + +### Behavior When Small + +- Instance tabs: Scroll horizontally +- Session tabs: Scroll horizontally +- Messages: Always visible, scroll vertically +- Input: Fixed at bottom + +## Accessibility + +- All interactive elements keyboard-navigable +- ARIA labels for screen readers +- Focus indicators visible +- Color contrast WCAG AA compliant +- Tab trap in modals +- Escape key closes overlays + +## Animation & Transitions + +- Tab switching: Instant (no animation) +- Message appearance: Fade in (100ms) +- Tool expand/collapse: Slide (200ms) +- Dropdown menus: Fade + slide (150ms) +- Loading states: Spinner or skeleton + +## Context Menus + +### Session Tab Right-Click + +- Rename +- Duplicate +- Share +- Export +- Delete +- Close Other Tabs + +### Message Right-Click + +- Copy message +- Copy code block +- Edit & regenerate +- Delete message +- Quote in reply + +## Status Indicators + +### Instance Tab + +- Green dot: Server running +- Yellow dot: Server starting +- Red dot: Server error +- No dot: Server stopped + +### Session Tab + +- Blue pulse: Assistant responding +- No indicator: Idle + +### Connection Status + +- Bottom right corner: "Connected" or "Reconnecting..." diff --git a/docs/screenshots/browser-support.png b/docs/screenshots/browser-support.png new file mode 100644 index 0000000..5bfb807 Binary files /dev/null and b/docs/screenshots/browser-support.png differ diff --git a/docs/screenshots/command-palette.png b/docs/screenshots/command-palette.png new file mode 100644 index 0000000..3847999 Binary files /dev/null and b/docs/screenshots/command-palette.png differ diff --git a/docs/screenshots/image-previews.png b/docs/screenshots/image-previews.png new file mode 100644 index 0000000..25bcd67 Binary files /dev/null and b/docs/screenshots/image-previews.png differ diff --git a/docs/screenshots/newSession.png b/docs/screenshots/newSession.png new file mode 100644 index 0000000..cb86e79 Binary files /dev/null and b/docs/screenshots/newSession.png differ diff --git a/images/CodeNomad-Icon-original.png b/images/CodeNomad-Icon-original.png new file mode 100644 index 0000000..9ce5d71 Binary files /dev/null and b/images/CodeNomad-Icon-original.png differ diff --git a/images/CodeNomad-Icon.png b/images/CodeNomad-Icon.png new file mode 100644 index 0000000..754b997 Binary files /dev/null and b/images/CodeNomad-Icon.png differ diff --git a/manual_test_guide.md b/manual_test_guide.md new file mode 100644 index 0000000..044bf08 --- /dev/null +++ b/manual_test_guide.md @@ -0,0 +1,76 @@ +# MultiX v2 - Verification & User Guide + +**Date:** 2025-12-25 +**Version:** 2.0.0 (Gold Master) + +--- + +## 1. Feature Verification Guide + +### A. Core Multi-Tasking & Parallel Execution +* **Goal:** Verify you can run multiple agents at once without freezing. +* **Steps:** + 1. Create **Task 1**. Type "Write a long story about space" and hit Launch. + 2. *While Task 1 is streaming*, click the **+** button to create **Task 2**. + 3. Type "Write a python script for fibonacci" in Task 2 and hit Launch. + 4. **Result:** Both tasks should stream simultaneously. Switching tabs should be instant. + +### B. Per-Task Isolation (Agents & Models) +* **Goal:** Verify each task retains its own settings. +* **Steps:** + 1. Go to **Task 1**. Select Agent: **"Software Engineer"** and Model: **"minimax-m2"**. + 2. Go to **Task 2**. Select Agent: **"Writer"** and Model: **"deepseek-chat"**. + 3. Switch back and forth. + 4. **Result:** The selectors should update to reflect the saved state for each task. + +### C. AI Agent Generator (NEW) +* **Goal:** Create a custom agent using AI. +* **Steps:** + 1. Open the **Agent Selector** dropdown. + 2. Click **"✨ AI Agent Generator"**. + 3. Type: *"A rust expert who is sarcastic and funny"*. + 4. Click **"Generate Agent"**. + 5. Review the generated name, description, and system prompt. + 6. Click **"Save & Use Agent"**. + 7. **Result:** The new agent is saved and immediately selected. + +### D. Prompt Enhancer +* **Goal:** strict Opus 4.5 prompt optimization. +* **Steps:** + 1. Type a simple prompt: *"fix bug"*. + 2. Click the **Magic Wand (✨)** button in the input area. + 3. **Result:** The prompt is expanded into a professional, structured request using the active model. + +### E. Compaction System +* **Goal:** Manage context window usage. +* **Steps:** + 1. In a long chat, look for the **"Compact suggested"** banner at the top of the chat list. + 2. Click **"Compact"** in the banner or the header bar. + 3. **Result:** The session history is summarized, freeing up tokens while keeping context. + +--- + +## 2. Menu & Wiring Check + +| Button | Wired Action | Status | +|--------|--------------|--------| +| **MULTIX Badge** | Visual Indicator | ✅ Active | +| **SKILLS** | Opens Sidebar (Events) | ✅ Wired | +| **Active Task** | Shows current task name | ✅ Wired | +| **Pipeline Tab** | Switches to Dashboard | ✅ Wired | +| **Task Tabs** | Switch/Close Tasks | ✅ Wired | +| **Compact Btn** | Triggers Compaction | ✅ Wired | +| **API Key Btn** | Opens Settings Modal | ✅ Wired | +| **Agent Select** | Updates Task Session | ✅ Wired | +| **Model Select** | Updates Task Session | ✅ Wired | + +--- + +## 3. Technical Status + +* **Build:** Passing (No TypeScript errors). +* **Dev Server:** Running on port 3001. +* **Architecture:** Polling-based (150ms sync) to prevent UI thread blocking. +* **State:** Local signals + Non-reactive store references. + +**Ready for deployment.** diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7d2ae4d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,10684 @@ +{ + "name": "codenomad-workspace", + "version": "0.5.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codenomad-workspace", + "version": "0.5.0", + "dependencies": { + "7zip-bin": "^5.2.0", + "google-auth-library": "^10.5.0" + }, + "devDependencies": { + "rollup": "^4.54.0" + }, + "optionalDependencies": { + "@esbuild/win32-x64": "^0.27.2" + }, + "workspaces": { + "packages": [ + "packages/*" + ] + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codenomad/tauri-app": { + "resolved": "packages/tauri-app", + "link": true + }, + "node_modules/@codenomad/ui": { + "resolved": "packages/ui", + "link": true + }, + "node_modules/@corvu/utils": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@corvu/utils/-/utils-0.4.2.tgz", + "integrity": "sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.6.11" + }, + "peerDependencies": { + "solid-js": "^1.8" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.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/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz", + "integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/notarize/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.0.5.tgz", + "integrity": "sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/osx-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/universal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz", + "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.1", + "@malept/cross-spawn-promise": "^1.1.0", + "debug": "^4.3.1", + "dir-compare": "^3.0.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "1.1.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/universal/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/accept-negotiator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-1.1.0.tgz", + "integrity": "sha512-OIHZrb2ImZ7XG85HXOONLcJWGosv7sIvM2ifAPQVhg9Lv7qdmMBNVaai4QTdyuaqbKM5eO6sLSQOYI7wEQeCJQ==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "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/@fastify/ajv-compiler/node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@fastify/cors": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.5.0.tgz", + "integrity": "sha512-/oZ1QSb02XjP0IK1U0IXktEsw/dUBTxJOW7IpIeO8c/tNalw/KjoNSJv1Sf6eqoBPO+TDGkifq6ynFK3v68HFQ==", + "license": "MIT", + "dependencies": { + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.6" + } + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@fastify/reply-from": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@fastify/reply-from/-/reply-from-9.8.0.tgz", + "integrity": "sha512-bPNVaFhEeNI0Lyl6404YZaPFokudCplidE3QoOcr78yOy6H9sYw97p5KPYvY/NJNUHfFtvxOaSAHnK+YSiv/Mg==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.0.0", + "end-of-stream": "^1.4.4", + "fast-content-type-parse": "^1.1.0", + "fast-querystring": "^1.0.0", + "fastify-plugin": "^4.0.0", + "toad-cache": "^3.7.0", + "undici": "^5.19.1" + } + }, + "node_modules/@fastify/reply-from/node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/@fastify/send": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@fastify/send/-/send-2.1.0.tgz", + "integrity": "sha512-yNYiY6sDkexoJR0D8IDy3aRP3+L4wdqCpvx5WP+VtEU58sn7USmKynBzDQex5X42Zzvw2gNzzYgP90UfWShLFA==", + "license": "MIT", + "dependencies": { + "@lukeed/ms": "^2.0.1", + "escape-html": "~1.0.3", + "fast-decode-uri-component": "^1.0.1", + "http-errors": "2.0.0", + "mime": "^3.0.0" + } + }, + "node_modules/@fastify/send/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@fastify/static": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@fastify/static/-/static-7.0.4.tgz", + "integrity": "sha512-p2uKtaf8BMOZWLs6wu+Ihg7bWNBdjNgCwDza4MJtTqg+5ovKmcbgbR9Xs5/smZ1YISfzKOCNYmZV8LaCj+eJ1Q==", + "license": "MIT", + "dependencies": { + "@fastify/accept-negotiator": "^1.0.0", + "@fastify/send": "^2.0.0", + "content-disposition": "^0.5.3", + "fastify-plugin": "^4.0.0", + "fastq": "^1.17.0", + "glob": "^10.3.4" + } + }, + "node_modules/@fastify/static/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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/@fastify/static/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@fastify/static/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@git-diff-view/core": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/@git-diff-view/core/-/core-0.0.35.tgz", + "integrity": "sha512-cdH3BopR6AWUW+6hP78zGyryKxR9JkPgryd1JN78i5k+F9Eo4x/4S23ZF1VZnrpPlGLrSuYfiAZ0ho5m+pTuKg==", + "license": "MIT", + "dependencies": { + "@git-diff-view/lowlight": "^0.0.35", + "fast-diff": "^1.3.0", + "highlight.js": "^11.11.0", + "lowlight": "^3.3.0" + } + }, + "node_modules/@git-diff-view/lowlight": { + "version": "0.0.35", + "resolved": "https://registry.npmjs.org/@git-diff-view/lowlight/-/lowlight-0.0.35.tgz", + "integrity": "sha512-MVpOxrNn1oHVOTOWUjxLbbf1W4OtVHjj6CHxwJbBRg9ZWZdShBINjuEgHVMSGB6vZuHKfwruRfXw8XxV3aF8zw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "highlight.js": "^11.11.0", + "lowlight": "^3.3.0" + } + }, + "node_modules/@git-diff-view/solid": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@git-diff-view/solid/-/solid-0.0.8.tgz", + "integrity": "sha512-MvZpyV5Gz0Axv2vvAlPpOmHtaJRUGBMoqXmvjIdZlUls0091QsglpE8bMbdRdEHuXodzxPDYyZrx3HCniMlGKw==", + "license": "MIT", + "dependencies": { + "@git-diff-view/core": "^0.0.35", + "@types/hast": "^3.0.0", + "fast-diff": "^1.3.0", + "highlight.js": "^11.11.0", + "lowlight": "^3.3.0", + "reactivity-store": "^0.3.12" + }, + "peerDependencies": { + "solid-js": "^1.9.0" + } + }, + "node_modules/@internationalized/date": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.10.0.tgz", + "integrity": "sha512-oxDR/NTEJ1k+UFVQElaNIk65E/Z83HK1z1WI3lQyhTtnNg4R5oVXaPzK3jcpKG8UHKDVuDQHzn+wsxSz8RP3aw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.5.tgz", + "integrity": "sha512-6hY4Kl4HPBvtfS62asS/R22JzNNy8vi/Ssev7x6EobfCp+9QIB2hKvI2EtbdJ0VSQacxVNtqhE/NmF/NZ0gm6g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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/@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/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@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/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/@kobalte/core": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/@kobalte/core/-/core-0.13.11.tgz", + "integrity": "sha512-hK7TYpdib/XDb/r/4XDBFaO9O+3ZHz4ZWryV4/3BfES+tSQVgg2IJupDnztKXB0BqbSRy/aWlHKw1SPtNPYCFQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.5.1", + "@internationalized/date": "^3.4.0", + "@internationalized/number": "^3.2.1", + "@kobalte/utils": "^0.9.1", + "@solid-primitives/props": "^3.1.8", + "@solid-primitives/resize-observer": "^2.0.26", + "solid-presence": "^0.1.8", + "solid-prevent-scroll": "^0.1.4" + }, + "peerDependencies": { + "solid-js": "^1.8.15" + } + }, + "node_modules/@kobalte/utils": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@kobalte/utils/-/utils-0.9.1.tgz", + "integrity": "sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w==", + "license": "MIT", + "dependencies": { + "@solid-primitives/event-listener": "^2.2.14", + "@solid-primitives/keyed": "^1.2.0", + "@solid-primitives/map": "^0.4.7", + "@solid-primitives/media": "^2.2.4", + "@solid-primitives/props": "^3.1.8", + "@solid-primitives/refs": "^1.0.5", + "@solid-primitives/utils": "^6.2.1" + }, + "peerDependencies": { + "solid-js": "^1.8.8" + } + }, + "node_modules/@lukeed/ms": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", + "integrity": "sha512-9I2Zn6+NJLfaGoz9jN3lpwDgAYvfGeNYdbAIjJOqzs4Tpc+VU3Jqq4IofSUBKajiDS8k9fZIg18/z13mpk1bsA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@neuralnomads/codenomad": { + "resolved": "packages/server", + "link": true + }, + "node_modules/@neuralnomads/codenomad-electron-app": { + "resolved": "packages/electron-app", + "link": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opencode-ai/plugin": { + "version": "1.0.180", + "resolved": "https://registry.npmjs.org/@opencode-ai/plugin/-/plugin-1.0.180.tgz", + "integrity": "sha512-u31txsEXmf6EpMRKYcY5S8ltwxLckXqvdxTICFaopFoT/vw7b1mPOxsm2II4QbmPa4uWnDNDt7HH8fh7PTF07w==", + "dependencies": { + "@opencode-ai/sdk": "1.0.180", + "zod": "4.1.8" + } + }, + "node_modules/@opencode-ai/plugin/node_modules/zod": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz", + "integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@opencode-ai/sdk": { + "version": "1.0.180", + "resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.0.180.tgz", + "integrity": "sha512-r3bocI5SR72FDORMC6O+oD9sKtEqUFgAboH14uwwJNF07xENxi8H1Bc9bmeYKdcJm4MVIURMLGBhUnOUhDMsuQ==" + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", + "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", + "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", + "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", + "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", + "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", + "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", + "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", + "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", + "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", + "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", + "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", + "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", + "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", + "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", + "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", + "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", + "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", + "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", + "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", + "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", + "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", + "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.15.0.tgz", + "integrity": "sha512-8TOG6yG557q+fMsSVa8nkEDOZNTSxjbbR8l6lF2gyr6Np+jrPlslqDxQkN6rMXCECQ3isNPZAGszAfYoJOPGlg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.15.0.tgz", + "integrity": "sha512-ZedbOFpopibdLmvTz2sJPJgns8Xvyabe2QbmqMTz07kt1pTzfEvKZc5IqPVO/XFiEbbNyaOpjPBkkr1vlwS+qg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.15.0.tgz", + "integrity": "sha512-HnqFsV11skAHvOArMZdLBZZApRSYS4LSztk2K3016Y9VCyZISnlYUYsL2hzlS7tPqKHvNqmI5JSUJZprXloMvA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.15.0.tgz", + "integrity": "sha512-WpRvEFvkVvO65uKYW4Rzxs+IG0gToyM8SARQMtGGsH4GDMNZrr60qdggXrFOsdfOVssG/QQGEl3FnJ3EZ+8w8A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.15.0.tgz", + "integrity": "sha512-8ow2zWb1IDvCKjYb0KiLNrK4offFdkfNVPXb1OZykpLCzRU6j+efkY+Y7VQjNlNFXonSw+4AOdGYtmqykDbRiQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.15.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.15.0.tgz", + "integrity": "sha512-BnP+y/EQnhihgHy4oIAN+6FFtmfTekwOLsQbRw9hOKwqgNy8Bdsjq8B05oAt/ZgvIWWFrshV71ytOrlPfYjIJw==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "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/@solid-primitives/event-listener": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@solid-primitives/event-listener/-/event-listener-2.4.3.tgz", + "integrity": "sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==", + "license": "MIT", + "dependencies": { + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/keyed": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@solid-primitives/keyed/-/keyed-1.5.2.tgz", + "integrity": "sha512-BgoEdqPw48URnI+L5sZIHdF4ua4Las1eWEBBPaoSFs42kkhnHue+rwCBPL2Z9ebOyQ75sUhUfOETdJfmv0D6Kg==", + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/map": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/@solid-primitives/map/-/map-0.4.13.tgz", + "integrity": "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew==", + "license": "MIT", + "dependencies": { + "@solid-primitives/trigger": "^1.1.0" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/media": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/@solid-primitives/media/-/media-2.3.3.tgz", + "integrity": "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA==", + "license": "MIT", + "dependencies": { + "@solid-primitives/event-listener": "^2.4.3", + "@solid-primitives/rootless": "^1.5.2", + "@solid-primitives/static-store": "^0.1.2", + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/props": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@solid-primitives/props/-/props-3.2.2.tgz", + "integrity": "sha512-lZOTwFJajBrshSyg14nBMEP0h8MXzPowGO0s3OeiR3z6nXHTfj0FhzDtJMv+VYoRJKQHG2QRnJTgCzK6erARAw==", + "license": "MIT", + "dependencies": { + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@solid-primitives/refs/-/refs-1.1.2.tgz", + "integrity": "sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg==", + "license": "MIT", + "dependencies": { + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/resize-observer": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@solid-primitives/resize-observer/-/resize-observer-2.1.3.tgz", + "integrity": "sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==", + "license": "MIT", + "dependencies": { + "@solid-primitives/event-listener": "^2.4.3", + "@solid-primitives/rootless": "^1.5.2", + "@solid-primitives/static-store": "^0.1.2", + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/rootless": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@solid-primitives/rootless/-/rootless-1.5.2.tgz", + "integrity": "sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==", + "license": "MIT", + "dependencies": { + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/static-store": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@solid-primitives/static-store/-/static-store-0.1.2.tgz", + "integrity": "sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==", + "license": "MIT", + "dependencies": { + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/trigger": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@solid-primitives/trigger/-/trigger-1.2.2.tgz", + "integrity": "sha512-IWoptVc0SWYgmpBPpCMehS5b07+tpFcvw15tOQ3QbXedSYn6KP8zCjPkHNzMxcOvOicTneleeZDP7lqmz+PQ6g==", + "license": "MIT", + "dependencies": { + "@solid-primitives/utils": "^6.3.2" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solid-primitives/utils": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.3.2.tgz", + "integrity": "sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==", + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.6.12" + } + }, + "node_modules/@solidjs/router": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/@solidjs/router/-/router-0.13.6.tgz", + "integrity": "sha512-CdpFsBYoiJ/FQ4wZIamj3KEFRkmrYu5sVXM6PouNkmSENta1YJamsm9wa/VjaPmkw2RsnDnO0UvZ705v6EgOXQ==", + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.8.6" + } + }, + "node_modules/@suid/base": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@suid/base/-/base-0.11.0.tgz", + "integrity": "sha512-jNe+LlXuxfkSZo8/MP9koqYYWswucDWSCwc7ViqUhQ0Y/V7sP2RiQ/Bnms+ePSMBZsk5k1b9fAjvj7DtNbbHXw==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.11.8", + "@suid/css": "0.4.1", + "@suid/system": "0.14.0", + "@suid/types": "0.8.0", + "@suid/utils": "0.11.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "solid-js": "^1.9.7" + } + }, + "node_modules/@suid/css": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@suid/css/-/css-0.4.1.tgz", + "integrity": "sha512-Hsi4O3dBOm7rrlqKoWfNoTeRFAXm/7TPaeEmyxNx+wFaT3eROjMVdhadAIiagFT+PsHrq/6fDauUI5TkL+5Zvg==", + "license": "MIT" + }, + "node_modules/@suid/icons-material": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@suid/icons-material/-/icons-material-0.9.0.tgz", + "integrity": "sha512-2idgaT/JARd12dwDfocZBQizaiZVgR0ujRsVc61OlAuPZbeH+3TrSxUJkE3Z7+TPftw9+6p0A24GhJjJLvi6RQ==", + "license": "MIT", + "dependencies": { + "@suid/material": "0.19.0" + }, + "peerDependencies": { + "solid-js": "^1.9.7" + } + }, + "node_modules/@suid/material": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/@suid/material/-/material-0.19.0.tgz", + "integrity": "sha512-vfudxYpHdur5CWTjd3eBb7q1b6A9X/pDWTEf2twc0gXVTcErS9VtY/VPBLa65AzO2SPJsdjAE+BCdVZiXASBbA==", + "license": "MIT", + "dependencies": { + "@suid/base": "0.11.0", + "@suid/css": "0.4.1", + "@suid/system": "0.14.0", + "@suid/types": "0.8.0", + "@suid/utils": "0.11.0", + "clsx": "^2.1.1" + }, + "peerDependencies": { + "solid-js": "^1.9.7" + } + }, + "node_modules/@suid/styled-engine": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@suid/styled-engine/-/styled-engine-0.9.0.tgz", + "integrity": "sha512-IfNHjQ3Im63mFIjFl/doiwdn5qbwgcwi/vUXnX7dmIUC/Cw1f3LPhzVT9V8Z3eqyvvFToy53O+BsuLy2e/WmDw==", + "license": "MIT", + "dependencies": { + "@suid/css": "0.4.1", + "@suid/utils": "0.11.0" + }, + "peerDependencies": { + "solid-js": "^1.9.7" + } + }, + "node_modules/@suid/system": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@suid/system/-/system-0.14.0.tgz", + "integrity": "sha512-aRVilPP53hHkqyAyQp2pasT/u8aQCcELwU4kFDnt3b+rj4fsPQRlhMumlX5mZ5aijIboH1CngU6TDG6Z9Mr3UA==", + "license": "MIT", + "dependencies": { + "@suid/css": "0.4.1", + "@suid/styled-engine": "0.9.0", + "@suid/types": "0.8.0", + "@suid/utils": "0.11.0", + "clsx": "^2.1.1", + "csstype": "^3.1.3" + }, + "peerDependencies": { + "solid-js": "^1.9.7" + } + }, + "node_modules/@suid/types": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@suid/types/-/types-0.8.0.tgz", + "integrity": "sha512-/Z2abkbypMjF6ygSpnjqnWohcmPqvgw8Xpx1wPPHeh+LajBP2imNT6uEa5dBqNEkJY8O3wEUCVqErAad/rmn5Q==", + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.9.7" + } + }, + "node_modules/@suid/utils": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@suid/utils/-/utils-0.11.0.tgz", + "integrity": "sha512-dk+6YJkex9kcU2qQHCOk8J0/zkOKKbng0SsjC0LBLyBrf2OC3OtDQq7o22pH3m/8CU/0M6uyM7tnyzZA4eWF3Q==", + "license": "MIT", + "dependencies": { + "@suid/types": "0.8.0" + }, + "peerDependencies": { + "solid-js": "^1.9.7" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "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/@tauri-apps/cli": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.4.tgz", + "integrity": "sha512-pvylWC9QckrOS9ATWXIXcgu7g2hKK5xTL5ZQyZU/U0n9l88SEFGcWgLQNa8WZmd+wWIOWhkxOFcOl3i6ubDNNw==", + "dev": true, + "license": "Apache-2.0 OR MIT", + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.9.4", + "@tauri-apps/cli-darwin-x64": "2.9.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.9.4", + "@tauri-apps/cli-linux-arm64-musl": "2.9.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.9.4", + "@tauri-apps/cli-linux-x64-gnu": "2.9.4", + "@tauri-apps/cli-linux-x64-musl": "2.9.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.9.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.9.4", + "@tauri-apps/cli-win32-x64-msvc": "2.9.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.4.tgz", + "integrity": "sha512-9rHkMVtbMhe0AliVbrGpzMahOBg3rwV46JYRELxR9SN6iu1dvPOaMaiC4cP6M/aD1424ziXnnMdYU06RAH8oIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.4.tgz", + "integrity": "sha512-VT9ymNuT06f5TLjCZW2hfSxbVtZDhORk7CDUDYiq5TiSYQdxkl8MVBy0CCFFcOk4QAkUmqmVUA9r3YZ/N/vPRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.4.tgz", + "integrity": "sha512-tTWkEPig+2z3Rk0zqZYfjUYcgD+aSm72wdrIhdYobxbQZOBw0zfn50YtWv+av7bm0SHvv75f0l7JuwgZM1HFow==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.4.tgz", + "integrity": "sha512-ql6vJ611qoqRYHxkKPnb2vHa27U+YRKRmIpLMMBeZnfFtZ938eao7402AQCH1mO2+/8ioUhbpy9R/ZcLTXVmkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.4.tgz", + "integrity": "sha512-vg7yNn7ICTi6hRrcA/6ff2UpZQP7un3xe3SEld5QM0prgridbKAiXGaCKr3BnUBx/rGXegQlD/wiLcWdiiraSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.4.tgz", + "integrity": "sha512-l8L+3VxNk6yv5T/Z/gv5ysngmIpsai40B9p6NQQyqYqxImqYX37pqREoEBl1YwG7szGnDibpWhidPrWKR59OJA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.4.tgz", + "integrity": "sha512-PepPhCXc/xVvE3foykNho46OmCyx47E/aG676vKTVp+mqin5d+IBqDL6wDKiGNT5OTTxKEyNlCQ81Xs2BQhhqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.4.tgz", + "integrity": "sha512-zcd1QVffh5tZs1u1SCKUV/V7RRynebgYUNWHuV0FsIF1MjnULUChEXhAhug7usCDq4GZReMJOoXa6rukEozWIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.4.tgz", + "integrity": "sha512-/7ZhnP6PY04bEob23q8MH/EoDISdmR1wuNm0k9d5HV7TDMd2GGCDa8dPXA4vJuglJKXIfXqxFmZ4L+J+MO42+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.4.tgz", + "integrity": "sha512-1LmAfaC4Cq+3O1Ir1ksdhczhdtFSTIV51tbAGtbV/mr348O+M52A/xwCCXQank0OcdBxy5BctqkMtuZnQvA8uQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.4.tgz", + "integrity": "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 OR MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.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/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/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/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "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/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/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "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": "22.19.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.0.tgz", + "integrity": "sha512-xpr/lmLPQEj+TUnHmR+Ab91/glhJvsqcjB+yY0Ix9GO70H6Lb4FHH5GeqdOE5btAx7eIMwuHkp4H2MSkLcqWbA==", + "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/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/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz", + "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.24" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.24", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.24.tgz", + "integrity": "sha512-9cwHL2EsJBdi8NY22pngYYWzkTDhld6fAD6jlaeloNGciNSJL6bLpbxVgXl96X00Jtc6YWQv96YA/0sxex/k1A==", + "license": "MIT" + }, + "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/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==", + "license": "MIT" + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "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/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "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": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz", + "integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.2.1", + "@electron/osx-sign": "1.0.5", + "@electron/universal": "1.5.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "electron-publish": "24.13.1", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "minimatch": "^5.1.1", + "read-config-file": "6.3.2", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "24.13.3", + "electron-builder-squirrel-windows": "24.13.3" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.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/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.21", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", + "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.24.4", + "caniuse-lite": "^1.0.30001702", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions": { + "version": "0.40.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jsx-dom-expressions/-/babel-plugin-jsx-dom-expressions-0.40.3.tgz", + "integrity": "sha512-5HOwwt0BYiv/zxl7j8Pf2bGL6rDXfV6nUhLs8ygBX+EFJXzBPHM/euj9j/6deMZ6wa52Wb2PBaAV5U/jKwIY1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "7.18.6", + "@babel/plugin-syntax-jsx": "^7.18.6", + "@babel/types": "^7.20.7", + "html-entities": "2.3.3", + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@babel/core": "^7.20.12" + } + }, + "node_modules/babel-plugin-jsx-dom-expressions/node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/babel-preset-solid": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/babel-preset-solid/-/babel-preset-solid-1.9.10.tgz", + "integrity": "sha512-HCelrgua/Y+kqO8RyL04JBWS/cVdrtUv/h45GntgQY+cJl4eBcKkCDV3TdMjtKx1nXwRaR9QXslM/Npm1dxdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jsx-dom-expressions": "^0.40.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "solid-js": "^1.9.10" + }, + "peerDependenciesMeta": { + "solid-js": { + "optional": true + } + } + }, + "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==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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.8.25", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.25.tgz", + "integrity": "sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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", + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", + "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.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" + }, + "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-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", + "integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "4.0.0", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz", + "integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001754", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz", + "integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==", + "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/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/chokidar/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/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "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==", + "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/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/config-file-ts": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz", + "integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.10", + "typescript": "^5.3.3" + } + }, + "node_modules/config-file-ts/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/config-file-ts/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/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dir-compare": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", + "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "^1.0.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.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.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dmg-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz", + "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "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.0.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-39.0.0.tgz", + "integrity": "sha512-UejnuOK4jpRZUq7MkEAnR/szsRWLKBJAdvn6j3xdQLT57fVv13VSNdaUHHjSheaqGzNhCGIdkPsPJnGJVh5kiA==", + "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": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz", + "integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "dmg-builder": "24.13.3", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "read-config-file": "6.3.2", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-24.13.3.tgz", + "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "24.13.3", + "archiver": "^5.3.1", + "builder-util": "24.13.1", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-publish": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", + "integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.247", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.247.tgz", + "integrity": "sha512-bCkfEJNE5EOhEsGpgbvgUAXYP/uAZeN9GCd9bPGjm2/uiLdpoiZnxrBTbWT1nbZs1y6UqN7hatM3hOB4X1kXUQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/electron-vite": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-4.0.1.tgz", + "integrity": "sha512-QqacJbA8f1pmwUTqki1qLL5vIBaOQmeq13CZZefZ3r3vKVaIoC7cpoTgE+KPKxJDFTax+iFZV0VYvLVWPiQ8Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.7", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "cac": "^6.7.14", + "esbuild": "^0.25.5", + "magic-string": "^0.30.17", + "picocolors": "^1.1.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "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==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "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/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/esbuild/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "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==", + "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/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "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-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/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/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-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "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/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-json-stringify/node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", + "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", + "license": "MIT" + }, + "node_modules/fastify/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "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/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/find-my-way": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", + "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "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/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/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/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/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gaxios/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==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "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/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-markdown-css": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.8.1.tgz", + "integrity": "sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/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/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.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/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "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/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/hosted-git-info/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/hosted-git-info/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/html-entities": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", + "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "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-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "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==", + "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-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-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-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==", + "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-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-what": { + "version": "4.1.16", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/isbinaryfile": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.6.tgz", + "integrity": "sha512-I+NmIfBHUl+r2wcDd6JwE9yWje/PIVY/R5/CmV8dXLZd5K+L9X2klAOwfAHNnondLXkbHyTAleQAWonpTJBTtw==", + "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==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "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/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "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/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/light-my-request": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", + "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.7.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lowlight": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", + "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "highlight.js": "~11.11.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-solid": { + "version": "0.300.0", + "resolved": "https://registry.npmjs.org/lucide-solid/-/lucide-solid-0.300.0.tgz", + "integrity": "sha512-FEAF0CYnuhQiFjpZe2UxQ0LX/wJFuPvFlyVwDdD4uvvAP/nzhCa1xSMhMBpaCTH3BIveJvoz14YOl7uiLkZYow==", + "license": "ISC", + "peerDependencies": { + "solid-js": "^1.4.7" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "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/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge-anything": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", + "integrity": "sha512-eRtbOb1N5iyH0tkQDAoQ4Ipsp/5qSR79Dzrz8hEPxRX10RWWR/iQXdoKmBSRCThY1Fh5EhISDtpSc93fpxUniQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^4.1.8" + }, + "engines": { + "node": ">=12.13" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "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-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/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/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mnemonist": { + "version": "0.39.6", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", + "integrity": "sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==", + "license": "MIT", + "dependencies": { + "obliterator": "^2.0.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "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/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-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "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/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-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "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/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/opencode-config": { + "resolved": "packages/opencode-config", + "link": true + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "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==", + "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==", + "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==", + "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==", + "license": "ISC" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/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/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pino/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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/png2icons": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/png2icons/-/png2icons-2.0.1.tgz", + "integrity": "sha512-GDEQJr8OG4e6JMp7mABtXFSEpgJa1CCpbQiAR+EjhkHJHnUL9zPPtbOrjsMD8gUbikgv3j7x404b0YJsV3aVFA==", + "dev": true, + "license": "MIT", + "bin": { + "png2icons": "png2icons-cli.js" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.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-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "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/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "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/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "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/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/qrcode/node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/qrcode/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/qrcode/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qrcode/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "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/react": { + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reactivity-store": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/reactivity-store/-/reactivity-store-0.3.12.tgz", + "integrity": "sha512-Idz9EL4dFUtQbHySZQzckWOTUfqjdYpUtNW0iOysC32mG7IjiUGB77QrsyR5eAWBkRiS9JscF6A3fuQAIy+LrQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "~3.5.22", + "@vue/shared": "~3.5.22", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-config-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", + "integrity": "sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-file-ts": "^0.2.4", + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/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/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "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-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "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/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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/rimraf/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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/rollup": { + "version": "4.54.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", + "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.54.0", + "@rollup/rollup-android-arm64": "4.54.0", + "@rollup/rollup-darwin-arm64": "4.54.0", + "@rollup/rollup-darwin-x64": "4.54.0", + "@rollup/rollup-freebsd-arm64": "4.54.0", + "@rollup/rollup-freebsd-x64": "4.54.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", + "@rollup/rollup-linux-arm-musleabihf": "4.54.0", + "@rollup/rollup-linux-arm64-gnu": "4.54.0", + "@rollup/rollup-linux-arm64-musl": "4.54.0", + "@rollup/rollup-linux-loong64-gnu": "4.54.0", + "@rollup/rollup-linux-ppc64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-gnu": "4.54.0", + "@rollup/rollup-linux-riscv64-musl": "4.54.0", + "@rollup/rollup-linux-s390x-gnu": "4.54.0", + "@rollup/rollup-linux-x64-gnu": "4.54.0", + "@rollup/rollup-linux-x64-musl": "4.54.0", + "@rollup/rollup-openharmony-arm64": "4.54.0", + "@rollup/rollup-win32-arm64-msvc": "4.54.0", + "@rollup/rollup-win32-ia32-msvc": "4.54.0", + "@rollup/rollup-win32-x64-gnu": "4.54.0", + "@rollup/rollup-win32-x64-msvc": "4.54.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "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": { + "queue-microtask": "^1.2.2" + } + }, + "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==", + "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/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "license": "MIT", + "dependencies": { + "ret": "~0.4.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "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.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/seroval": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.3.3.tgz", + "integrity": "sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.15.0.tgz", + "integrity": "sha512-kLdkY6iV3dYbtPwS9KXU7mjfmDm25f5m0IPNFnaXO7TBPcvbUOY72PYXSuSqDzwp+vlH/d7MXpHlKO/x+QoLXw==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.15.0", + "@shikijs/engine-javascript": "3.15.0", + "@shikijs/engine-oniguruma": "3.15.0", + "@shikijs/langs": "3.15.0", + "@shikijs/themes": "3.15.0", + "@shikijs/types": "3.15.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "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==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.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", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/solid-js": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", + "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.0", + "seroval": "~1.3.0", + "seroval-plugins": "~1.3.0" + } + }, + "node_modules/solid-presence": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/solid-presence/-/solid-presence-0.1.8.tgz", + "integrity": "sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA==", + "license": "MIT", + "dependencies": { + "@corvu/utils": "~0.4.0" + }, + "peerDependencies": { + "solid-js": "^1.8" + } + }, + "node_modules/solid-prevent-scroll": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/solid-prevent-scroll/-/solid-prevent-scroll-0.1.10.tgz", + "integrity": "sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw==", + "license": "MIT", + "dependencies": { + "@corvu/utils": "~0.4.1" + }, + "peerDependencies": { + "solid-js": "^1.8" + } + }, + "node_modules/solid-refresh": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/solid-refresh/-/solid-refresh-0.6.3.tgz", + "integrity": "sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "^7.23.6", + "@babel/helper-module-imports": "^7.22.15", + "@babel/types": "^7.23.6" + }, + "peerDependencies": { + "solid-js": "^1.3" + } + }, + "node_modules/solid-toast": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/solid-toast/-/solid-toast-0.5.0.tgz", + "integrity": "sha512-t770JakjyS2P9b8Qa1zMLOD51KYKWXbTAyJePVUoYex5c5FH5S/HtUBUbZAWFcqRCKmAE8KhyIiCvDZA8bOnxQ==", + "license": "MIT", + "peerDependencies": { + "solid-js": "^1.5.4" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-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/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "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", + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "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==", + "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/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "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/sucrase/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/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/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/tailwindcss": { + "version": "3.4.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", + "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/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/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/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/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/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "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/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/ulid": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/ulid/-/ulid-3.0.2.tgz", + "integrity": "sha512-yu26mwteFYzBAot7KVMqFGCVpsF6g8wXfJzQUHvu1no3+rRRSFcSV2nKeYvNPLD2J4b08jYBDhHUjeH0ygIl9w==", + "license": "MIT", + "bin": { + "ulid": "dist/cli.js" + } + }, + "node_modules/undici": { + "version": "6.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.22.0.tgz", + "integrity": "sha512-hU/10obOIu62MGYjdskASR3CUAiYaFTtC9Pa6vHyf//mAipSvSQg6od2CnJswq7fvzNS3zJhxoRkgNVaHurWKw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "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/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/update-browserslist-db": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "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/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "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/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-solid": { + "version": "2.11.10", + "resolved": "https://registry.npmjs.org/vite-plugin-solid/-/vite-plugin-solid-2.11.10.tgz", + "integrity": "sha512-Yr1dQybmtDtDAHkii6hXuc1oVH9CPcS/Zb2jN/P36qqcrkNnVPsMTzQ06jyzFPFjj3U1IYKMVt/9ZqcwGCEbjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.3", + "@types/babel__core": "^7.20.4", + "babel-preset-solid": "^1.8.4", + "merge-anything": "^5.1.7", + "solid-refresh": "^0.6.3", + "vitefu": "^1.0.4" + }, + "peerDependencies": { + "@testing-library/jest-dom": "^5.16.6 || ^5.17.0 || ^6.*", + "solid-js": "^1.7.2", + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "@testing-library/jest-dom": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitefu": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.1.tgz", + "integrity": "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "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==", + "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==", + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "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/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "packages/electron-app": { + "name": "@neuralnomads/codenomad-electron-app", + "version": "0.4.0", + "dependencies": { + "@codenomad/ui": "file:../ui", + "@neuralnomads/codenomad": "file:../server" + }, + "devDependencies": { + "7zip-bin": "^5.2.0", + "app-builder-bin": "^4.2.0", + "cross-env": "^7.0.3", + "electron": "39.0.0", + "electron-builder": "^24.0.0", + "electron-vite": "4.0.1", + "png2icons": "^2.0.1", + "pngjs": "^7.0.0", + "tsx": "^4.20.6", + "typescript": "^5.3.0", + "vite": "^5.0.0", + "vite-plugin-solid": "^2.10.0" + } + }, + "packages/electron-app/node_modules/app-builder-bin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.2.0.tgz", + "integrity": "sha512-PGXlkukQnroTgAaDZnnppdLzsRJmab6Rh/rJ5fKyYaYhd+FfaORH59/ArkB5dr2cAeYQU5lCeHFEwURaoBO8BA==", + "dev": true, + "license": "MIT" + }, + "packages/opencode-config": { + "dependencies": { + "@opencode-ai/plugin": "1.0.180" + } + }, + "packages/server": { + "name": "@neuralnomads/codenomad", + "version": "0.4.0", + "dependencies": { + "@fastify/cors": "^8.5.0", + "@fastify/reply-from": "^9.8.0", + "@fastify/static": "^7.0.4", + "commander": "^12.1.0", + "fastify": "^4.28.1", + "fuzzysort": "^2.0.4", + "pino": "^9.4.0", + "ulid": "^3.0.2", + "undici": "^6.19.8", + "zod": "^3.23.8" + }, + "bin": { + "codenomad": "dist/bin.js" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "ts-node": "^10.9.2", + "tsx": "^4.20.6", + "typescript": "^5.6.3" + } + }, + "packages/server/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "packages/server/node_modules/fuzzysort": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-2.0.4.tgz", + "integrity": "sha512-Api1mJL+Ad7W7vnDZnWq5pGaXJjyencT+iKGia2PlHUcSsSzWwIQ3S1isiMpwpavjYtGd2FzhUIhnnhOULZgDw==", + "license": "MIT" + }, + "packages/tauri-app": { + "name": "@codenomad/tauri-app", + "version": "0.4.0", + "devDependencies": { + "@tauri-apps/cli": "^2.9.4" + } + }, + "packages/ui": { + "name": "@codenomad/ui", + "version": "0.4.0", + "dependencies": { + "@git-diff-view/solid": "^0.0.8", + "@kobalte/core": "0.13.11", + "@opencode-ai/sdk": "^1.0.138", + "@solidjs/router": "^0.13.0", + "@suid/icons-material": "^0.9.0", + "@suid/material": "^0.19.0", + "@suid/system": "^0.14.0", + "debug": "^4.4.3", + "github-markdown-css": "^5.8.1", + "lucide-solid": "^0.300.0", + "marked": "^12.0.0", + "qrcode": "^1.5.3", + "shiki": "^3.13.0", + "solid-js": "^1.8.0", + "solid-toast": "^0.5.0" + }, + "devDependencies": { + "autoprefixer": "10.4.21", + "postcss": "8.5.6", + "tailwindcss": "3", + "tsx": "^4.21.0", + "typescript": "^5.3.0", + "vite": "^5.0.0", + "vite-plugin-solid": "^2.10.0", + "zod": "^3.25.76" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ff0a055 --- /dev/null +++ b/package.json @@ -0,0 +1,33 @@ +{ + "name": "codenomad-workspace", + "version": "0.5.0", + "private": true, + "description": "CodeNomad monorepo workspace", + "workspaces": { + "packages": [ + "packages/*" + ] + }, + "scripts": { + "dev": "npm run dev:electron --workspace @neuralnomads/codenomad-electron-app", + "dev:electron": "npm run dev:electron --workspace @neuralnomads/codenomad-electron-app", + "dev:tauri": "npm run dev --workspace @codenomad/tauri-app", + "build": "npm run build --workspace @neuralnomads/codenomad-electron-app", + "build:tauri": "npm run build --workspace @codenomad/tauri-app", + "build:ui": "npm run build --workspace @codenomad/ui", + "build:mac-x64": "npm run build:mac-x64 --workspace @neuralnomads/codenomad-electron-app", + "build:binaries": "npm run build:binaries --workspace @neuralnomads/codenomad-electron-app", + "typecheck": "npm run typecheck --workspace @codenomad/ui && npm run typecheck --workspace @neuralnomads/codenomad-electron-app", + "bumpVersion": "npm version --workspaces --include-workspace-root --no-git-tag-version" + }, + "dependencies": { + "7zip-bin": "^5.2.0", + "google-auth-library": "^10.5.0" + }, + "devDependencies": { + "rollup": "^4.54.0" + }, + "optionalDependencies": { + "@esbuild/win32-x64": "^0.27.2" + } +} \ No newline at end of file diff --git a/packages/electron-app/.gitignore b/packages/electron-app/.gitignore new file mode 100644 index 0000000..2dfa475 --- /dev/null +++ b/packages/electron-app/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +release/ +.vite/ diff --git a/packages/electron-app/README.md b/packages/electron-app/README.md new file mode 100644 index 0000000..6e9a680 --- /dev/null +++ b/packages/electron-app/README.md @@ -0,0 +1,40 @@ +# CodeNomad App + +This package contains the native desktop application shell for CodeNomad, built with [Electron](https://www.electronjs.org/). + +## Overview + +The Electron app wraps the CodeNomad UI and Server into a standalone executable. It provides deeper system integration, such as: +- Native window management +- Global keyboard shortcuts +- Application menu integration + +## Development + +To run the Electron app in development mode: + +```bash +npm run dev +``` + +This will start the renderer (UI) and the main process with hot reloading. + +## Building + +To build the application for your current platform: + +```bash +npm run build +``` + +To build for specific platforms (requires appropriate build tools): + +- **macOS**: `npm run build:mac` +- **Windows**: `npm run build:win` +- **Linux**: `npm run build:linux` + +## Structure + +- `electron/main`: Main process code (window creation, IPC). +- `electron/preload`: Preload scripts for secure bridge between main and renderer. +- `electron/resources`: Static assets like icons. diff --git a/packages/electron-app/electron.vite.config.ts b/packages/electron-app/electron.vite.config.ts new file mode 100644 index 0000000..1161176 --- /dev/null +++ b/packages/electron-app/electron.vite.config.ts @@ -0,0 +1,72 @@ +import { defineConfig, externalizeDepsPlugin } from "electron-vite" +import solid from "vite-plugin-solid" +import { resolve } from "path" + +const uiRoot = resolve(__dirname, "../ui") +const uiSrc = resolve(uiRoot, "src") +const uiRendererRoot = resolve(uiRoot, "src/renderer") +const uiRendererEntry = resolve(uiRendererRoot, "index.html") +const uiRendererLoadingEntry = resolve(uiRendererRoot, "loading.html") + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + build: { + outDir: "dist/main", + lib: { + entry: resolve(__dirname, "electron/main/main.ts"), + }, + rollupOptions: { + external: ["electron"], + }, + }, + }, + preload: { + plugins: [externalizeDepsPlugin()], + build: { + outDir: "dist/preload", + lib: { + entry: resolve(__dirname, "electron/preload/index.cjs"), + formats: ["cjs"], + fileName: () => "index.js", + }, + rollupOptions: { + external: ["electron"], + output: { + entryFileNames: "index.js", + }, + }, + }, + }, + renderer: { + root: uiRendererRoot, + plugins: [solid()], + css: { + postcss: resolve(uiRoot, "postcss.config.js"), + }, + resolve: { + alias: { + "@": uiSrc, + }, + }, + server: { + port: 3000, + }, + build: { + minify: false, + cssMinify: false, + sourcemap: true, + outDir: resolve(__dirname, "dist/renderer"), + rollupOptions: { + input: { + main: uiRendererEntry, + loading: uiRendererLoadingEntry, + }, + output: { + compact: false, + minifyInternalExports: false, + }, + }, + }, + }, +}) diff --git a/packages/electron-app/electron/main/ipc.ts b/packages/electron-app/electron/main/ipc.ts new file mode 100644 index 0000000..1d15fd3 --- /dev/null +++ b/packages/electron-app/electron/main/ipc.ts @@ -0,0 +1,112 @@ +import { BrowserWindow, dialog, ipcMain, type OpenDialogOptions } from "electron" +import path from "path" +import type { CliProcessManager, CliStatus } from "./process-manager" +import { + listUsers, + createUser, + updateUser, + deleteUser, + verifyPassword, + setActiveUser, + createGuestUser, + getActiveUser, + getUserDataRoot, +} from "./user-store" + +interface DialogOpenRequest { + mode: "directory" | "file" + title?: string + defaultPath?: string + filters?: Array<{ name?: string; extensions: string[] }> +} + +interface DialogOpenResult { + canceled: boolean + paths: string[] +} + +export function setupCliIPC(mainWindow: BrowserWindow, cliManager: CliProcessManager) { + cliManager.on("status", (status: CliStatus) => { + if (!mainWindow.isDestroyed()) { + mainWindow.webContents.send("cli:status", status) + } + }) + + cliManager.on("ready", (status: CliStatus) => { + if (!mainWindow.isDestroyed()) { + mainWindow.webContents.send("cli:ready", status) + } + }) + + cliManager.on("error", (error: Error) => { + if (!mainWindow.isDestroyed()) { + mainWindow.webContents.send("cli:error", { message: error.message }) + } + }) + + ipcMain.handle("cli:getStatus", async () => cliManager.getStatus()) + + ipcMain.handle("cli:restart", async () => { + const devMode = process.env.NODE_ENV === "development" + await cliManager.stop() + return cliManager.start({ dev: devMode }) + }) + + ipcMain.handle("users:list", async () => listUsers()) + ipcMain.handle("users:active", async () => getActiveUser()) + ipcMain.handle("users:create", async (_, payload: { name: string; password: string }) => { + const user = createUser(payload.name, payload.password) + return user + }) + ipcMain.handle("users:update", async (_, payload: { id: string; name?: string; password?: string }) => { + const user = updateUser(payload.id, { name: payload.name, password: payload.password }) + return user + }) + ipcMain.handle("users:delete", async (_, payload: { id: string }) => { + deleteUser(payload.id) + return { success: true } + }) + ipcMain.handle("users:createGuest", async () => { + const user = createGuestUser() + return user + }) + ipcMain.handle("users:login", async (_, payload: { id: string; password?: string }) => { + const ok = verifyPassword(payload.id, payload.password ?? "") + if (!ok) { + return { success: false } + } + const user = setActiveUser(payload.id) + const root = getUserDataRoot(user.id) + cliManager.setUserEnv({ + CODENOMAD_USER_DIR: root, + CLI_CONFIG: path.join(root, "config.json"), + }) + await cliManager.stop() + const devMode = process.env.NODE_ENV === "development" + await cliManager.start({ dev: devMode }) + return { success: true, user } + }) + + ipcMain.handle("dialog:open", async (_, request: DialogOpenRequest): Promise => { + const properties: OpenDialogOptions["properties"] = + request.mode === "directory" ? ["openDirectory", "createDirectory"] : ["openFile"] + + const filters = request.filters?.map((filter) => ({ + name: filter.name ?? "Files", + extensions: filter.extensions, + })) + + const windowTarget = mainWindow.isDestroyed() ? undefined : mainWindow + const dialogOptions: OpenDialogOptions = { + title: request.title, + defaultPath: request.defaultPath, + properties, + filters, + } + const result = windowTarget + ? await dialog.showOpenDialog(windowTarget, dialogOptions) + : await dialog.showOpenDialog(dialogOptions) + + return { canceled: result.canceled, paths: result.filePaths } + }) +} diff --git a/packages/electron-app/electron/main/main.ts b/packages/electron-app/electron/main/main.ts new file mode 100644 index 0000000..b412e3d --- /dev/null +++ b/packages/electron-app/electron/main/main.ts @@ -0,0 +1,522 @@ +import { app, BrowserView, BrowserWindow, nativeImage, session, shell } from "electron" +import { existsSync } from "fs" +import { dirname, join } from "path" +import { fileURLToPath } from "url" +import { createApplicationMenu } from "./menu" +import { setupCliIPC } from "./ipc" +import { CliProcessManager } from "./process-manager" +import { ensureDefaultUsers, getActiveUser, getUserDataRoot, clearGuestUsers } from "./user-store" + +const mainFilename = fileURLToPath(import.meta.url) +const mainDirname = dirname(mainFilename) + +const isMac = process.platform === "darwin" + +const cliManager = new CliProcessManager() +let mainWindow: BrowserWindow | null = null +let currentCliUrl: string | null = null +let pendingCliUrl: string | null = null +let showingLoadingScreen = false +let preloadingView: BrowserView | null = null + +// Retry logic constants +const MAX_RETRY_ATTEMPTS = 5 +const LOAD_TIMEOUT_MS = 30000 +let retryAttempts = 0 + +if (isMac) { + app.commandLine.appendSwitch("disable-spell-checking") +} + +// Windows: Use Edge WebView2 rendering for better performance +if (process.platform === "win32") { + app.commandLine.appendSwitch("enable-features", "WebViewTagWebComponent,WebView2") + app.commandLine.appendSwitch("disable-gpu-sandbox") + app.commandLine.appendSwitch("enable-gpu-rasterization") + app.commandLine.appendSwitch("enable-zero-copy") + app.commandLine.appendSwitch("disable-background-timer-throttling") + app.commandLine.appendSwitch("disable-renderer-backgrounding") +} + +function getIconPath() { + if (app.isPackaged) { + return join(process.resourcesPath, "icon.png") + } + + return join(mainDirname, "../resources/icon.png") +} + +type LoadingTarget = + | { type: "url"; source: string } + | { type: "file"; source: string } + +function resolveDevLoadingUrl(): string | null { + if (app.isPackaged) { + return null + } + const devBase = process.env.VITE_DEV_SERVER_URL || process.env.ELECTRON_RENDERER_URL + if (!devBase) { + return null + } + + try { + const normalized = devBase.endsWith("/") ? devBase : `${devBase}/` + return new URL("loading.html", normalized).toString() + } catch (error) { + console.warn("[cli] failed to construct dev loading URL", devBase, error) + return null + } +} + +function resolveLoadingTarget(): LoadingTarget { + const devUrl = resolveDevLoadingUrl() + if (devUrl) { + return { type: "url", source: devUrl } + } + const filePath = resolveLoadingFilePath() + return { type: "file", source: filePath } +} + +function resolveLoadingFilePath() { + const candidates = [ + join(app.getAppPath(), "dist/renderer/loading.html"), + join(process.resourcesPath, "dist/renderer/loading.html"), + join(mainDirname, "../dist/renderer/loading.html"), + ] + + for (const candidate of candidates) { + if (existsSync(candidate)) { + return candidate + } + } + + return join(app.getAppPath(), "dist/renderer/loading.html") +} + +function loadLoadingScreen(window: BrowserWindow) { + const target = resolveLoadingTarget() + const loader = + target.type === "url" + ? window.loadURL(target.source) + : window.loadFile(target.source) + + loader.catch((error) => { + console.error("[cli] failed to load loading screen:", error) + }) +} + +// Calculate exponential backoff delay +function getRetryDelay(attempt: number): number { + return Math.min(1000 * Math.pow(2, attempt), 16000) // 1s, 2s, 4s, 8s, 16s max +} + +// Show user-friendly error screen +function showErrorScreen(window: BrowserWindow, errorMessage: string) { + const errorHtml = ` + + + + + + +
⚠️
+

Connection Failed

+

NomadArch couldn't connect to the development server after multiple attempts. Please ensure the server is running.

+
${errorMessage}
+ + + + ` + window.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(errorHtml)}`) +} + +function getAllowedRendererOrigins(): string[] { + const origins = new Set() + const rendererCandidates = [currentCliUrl, process.env.VITE_DEV_SERVER_URL, process.env.ELECTRON_RENDERER_URL] + for (const candidate of rendererCandidates) { + if (!candidate) { + continue + } + try { + origins.add(new URL(candidate).origin) + } catch (error) { + console.warn("[cli] failed to parse origin for", candidate, error) + } + } + return Array.from(origins) +} + +function shouldOpenExternally(url: string): boolean { + try { + const parsed = new URL(url) + if (parsed.protocol !== "http:" && parsed.protocol !== "https:") { + return true + } + const allowedOrigins = getAllowedRendererOrigins() + return !allowedOrigins.includes(parsed.origin) + } catch { + return false + } +} + +function setupNavigationGuards(window: BrowserWindow) { + const handleExternal = (url: string) => { + shell.openExternal(url).catch((error) => console.error("[cli] failed to open external URL", url, error)) + } + + window.webContents.setWindowOpenHandler(({ url }) => { + if (shouldOpenExternally(url)) { + handleExternal(url) + return { action: "deny" } + } + return { action: "allow" } + }) + + window.webContents.on("will-navigate", (event, url) => { + if (shouldOpenExternally(url)) { + event.preventDefault() + handleExternal(url) + } + }) +} + +let cachedPreloadPath: string | null = null +function getPreloadPath() { + if (cachedPreloadPath && existsSync(cachedPreloadPath)) { + return cachedPreloadPath + } + + const candidates = [ + join(process.resourcesPath, "preload/index.js"), + join(mainDirname, "../preload/index.js"), + join(mainDirname, "../preload/index.cjs"), + join(mainDirname, "../../preload/index.cjs"), + join(mainDirname, "../../electron/preload/index.cjs"), + join(app.getAppPath(), "preload/index.cjs"), + join(app.getAppPath(), "electron/preload/index.cjs"), + ] + + for (const candidate of candidates) { + if (existsSync(candidate)) { + cachedPreloadPath = candidate + return candidate + } + } + + return join(mainDirname, "../preload/index.js") +} + +function applyUserEnvToCli() { + const active = getActiveUser() + if (!active) { + const fallback = ensureDefaultUsers() + const fallbackRoot = getUserDataRoot(fallback.id) + cliManager.setUserEnv({ + CODENOMAD_USER_DIR: fallbackRoot, + CLI_CONFIG: join(fallbackRoot, "config.json"), + }) + return + } + const root = getUserDataRoot(active.id) + cliManager.setUserEnv({ + CODENOMAD_USER_DIR: root, + CLI_CONFIG: join(root, "config.json"), + }) +} + +function destroyPreloadingView(target?: BrowserView | null) { + const view = target ?? preloadingView + if (!view) { + return + } + + try { + const contents = view.webContents as any + contents?.destroy?.() + } catch (error) { + console.warn("[cli] failed to destroy preloading view", error) + } + + if (!target || view === preloadingView) { + preloadingView = null + } +} + +function createWindow() { + const prefersDark = true + const backgroundColor = prefersDark ? "#1a1a1a" : "#ffffff" + const iconPath = getIconPath() + + mainWindow = new BrowserWindow({ + width: 1400, + height: 900, + minWidth: 800, + minHeight: 600, + backgroundColor, + icon: iconPath, + title: "NomadArch 1.0", + webPreferences: { + preload: getPreloadPath(), + contextIsolation: true, + nodeIntegration: false, + spellcheck: !isMac, + }, + }) + + setupNavigationGuards(mainWindow) + + if (isMac) { + mainWindow.webContents.session.setSpellCheckerEnabled(false) + } + + showingLoadingScreen = true + currentCliUrl = null + loadLoadingScreen(mainWindow) + + if (process.env.NODE_ENV === "development" && process.env.NOMADARCH_OPEN_DEVTOOLS === "true") { + mainWindow.webContents.openDevTools({ mode: "detach" }) + } + + createApplicationMenu(mainWindow) + setupCliIPC(mainWindow, cliManager) + + mainWindow.on("closed", () => { + destroyPreloadingView() + mainWindow = null + currentCliUrl = null + pendingCliUrl = null + showingLoadingScreen = false + }) + + if (pendingCliUrl) { + const url = pendingCliUrl + pendingCliUrl = null + startCliPreload(url) + } +} + +function showLoadingScreen(force = false) { + if (!mainWindow || mainWindow.isDestroyed()) { + return + } + + if (showingLoadingScreen && !force) { + return + } + + destroyPreloadingView() + showingLoadingScreen = true + currentCliUrl = null + pendingCliUrl = null + loadLoadingScreen(mainWindow) +} + +function startCliPreload(url: string) { + if (!mainWindow || mainWindow.isDestroyed()) { + pendingCliUrl = url + return + } + + if (currentCliUrl === url && !showingLoadingScreen) { + return + } + + pendingCliUrl = url + destroyPreloadingView() + + if (!showingLoadingScreen) { + showLoadingScreen(true) + } + + const view = new BrowserView({ + webPreferences: { + contextIsolation: true, + nodeIntegration: false, + spellcheck: !isMac, + }, + }) + + preloadingView = view + + view.webContents.once("did-finish-load", () => { + if (preloadingView !== view) { + destroyPreloadingView(view) + return + } + finalizeCliSwap(url) + }) + + view.webContents.loadURL(url).catch((error) => { + console.error("[cli] failed to preload CLI view:", error) + if (preloadingView === view) { + destroyPreloadingView(view) + } + }) +} + +function finalizeCliSwap(url: string) { + destroyPreloadingView() + + if (!mainWindow || mainWindow.isDestroyed()) { + pendingCliUrl = url + return + } + + showingLoadingScreen = false + currentCliUrl = url + pendingCliUrl = null + + // Reset retry counter on new URL + retryAttempts = 0 + + const loadWithRetry = () => { + if (!mainWindow || mainWindow.isDestroyed()) return + + // Set timeout for load + const timeoutId = setTimeout(() => { + console.warn(`[cli] Load timeout after ${LOAD_TIMEOUT_MS}ms`) + handleLoadError(new Error(`Load timeout after ${LOAD_TIMEOUT_MS}ms`)) + }, LOAD_TIMEOUT_MS) + + mainWindow.loadURL(url) + .then(() => { + clearTimeout(timeoutId) + retryAttempts = 0 // Reset on success + console.info("[cli] Successfully loaded CLI view") + }) + .catch((error) => { + clearTimeout(timeoutId) + handleLoadError(error) + }) + } + + const handleLoadError = (error: Error) => { + const errorCode = (error as any).errno + console.error(`[cli] failed to load CLI view (attempt ${retryAttempts + 1}/${MAX_RETRY_ATTEMPTS}):`, error.message) + + // Retry on network errors (errno -3) + if (errorCode === -3 && retryAttempts < MAX_RETRY_ATTEMPTS) { + retryAttempts++ + const delay = getRetryDelay(retryAttempts) + console.info(`[cli] Retrying in ${delay}ms (attempt ${retryAttempts}/${MAX_RETRY_ATTEMPTS})`) + + if (mainWindow && !mainWindow.isDestroyed()) { + loadLoadingScreen(mainWindow) + } + + setTimeout(loadWithRetry, delay) + } else if (retryAttempts >= MAX_RETRY_ATTEMPTS) { + console.error("[cli] Max retry attempts reached, showing error screen") + if (mainWindow && !mainWindow.isDestroyed()) { + showErrorScreen(mainWindow, `Failed after ${MAX_RETRY_ATTEMPTS} attempts: ${error.message}`) + } + } + } + + loadWithRetry() +} + + +async function startCli() { + try { + const devMode = process.env.NODE_ENV === "development" + console.info("[cli] start requested (dev mode:", devMode, ")") + await cliManager.start({ dev: devMode }) + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + console.error("[cli] start failed:", message) + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.webContents.send("cli:error", { message }) + } + } +} + +cliManager.on("ready", (status) => { + if (!status.url) { + return + } + startCliPreload(status.url) +}) + +cliManager.on("status", (status) => { + if (status.state !== "ready") { + showLoadingScreen() + } +}) + +if (isMac) { + app.on("web-contents-created", (_, contents) => { + contents.session.setSpellCheckerEnabled(false) + }) +} + +app.whenReady().then(() => { + ensureDefaultUsers() + applyUserEnvToCli() + startCli() + + if (isMac) { + session.defaultSession.setSpellCheckerEnabled(false) + app.on("browser-window-created", (_, window) => { + window.webContents.session.setSpellCheckerEnabled(false) + }) + + if (app.dock) { + const dockIcon = nativeImage.createFromPath(getIconPath()) + if (!dockIcon.isEmpty()) { + app.dock.setIcon(dockIcon) + } + } + } + + createWindow() + + app.on("activate", () => { + if (BrowserWindow.getAllWindows().length === 0) { + createWindow() + } + }) +}) + +app.on("before-quit", async (event) => { + event.preventDefault() + await cliManager.stop().catch(() => { }) + clearGuestUsers() + app.exit(0) +}) + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit() + } +}) diff --git a/packages/electron-app/electron/main/menu.ts b/packages/electron-app/electron/main/menu.ts new file mode 100644 index 0000000..37f9e34 --- /dev/null +++ b/packages/electron-app/electron/main/menu.ts @@ -0,0 +1,84 @@ +import { Menu, BrowserWindow, MenuItemConstructorOptions } from "electron" + +export function createApplicationMenu(mainWindow: BrowserWindow) { + const isMac = process.platform === "darwin" + + const template: MenuItemConstructorOptions[] = [ + ...(isMac + ? [ + { + label: "CodeNomad", + submenu: [ + { role: "about" as const }, + { type: "separator" as const }, + { role: "hide" as const }, + { role: "hideOthers" as const }, + { role: "unhide" as const }, + { type: "separator" as const }, + { role: "quit" as const }, + ], + }, + ] + : []), + { + label: "File", + submenu: [ + { + label: "New Instance", + accelerator: "CmdOrCtrl+N", + click: () => { + mainWindow.webContents.send("menu:newInstance") + }, + }, + { type: "separator" as const }, + isMac ? { role: "close" as const } : { role: "quit" as const }, + ], + }, + { + label: "Edit", + submenu: [ + { role: "undo" as const }, + { role: "redo" as const }, + { type: "separator" as const }, + { role: "cut" as const }, + { role: "copy" as const }, + { role: "paste" as const }, + ...(isMac + ? [{ role: "pasteAndMatchStyle" as const }, { role: "delete" as const }, { role: "selectAll" as const }] + : [{ role: "delete" as const }, { type: "separator" as const }, { role: "selectAll" as const }]), + ], + }, + { + label: "View", + submenu: [ + { role: "reload" as const }, + { role: "forceReload" as const }, + { role: "toggleDevTools" as const }, + { type: "separator" as const }, + { role: "resetZoom" as const }, + { role: "zoomIn" as const }, + { role: "zoomOut" as const }, + { type: "separator" as const }, + { role: "togglefullscreen" as const }, + ], + }, + { + label: "Window", + submenu: [ + { role: "minimize" as const }, + { role: "zoom" as const }, + ...(isMac + ? [ + { type: "separator" as const }, + { role: "front" as const }, + { type: "separator" as const }, + { role: "window" as const }, + ] + : [{ role: "close" as const }]), + ], + }, + ] + + const menu = Menu.buildFromTemplate(template) + Menu.setApplicationMenu(menu) +} diff --git a/packages/electron-app/electron/main/process-manager.ts b/packages/electron-app/electron/main/process-manager.ts new file mode 100644 index 0000000..fad30a5 --- /dev/null +++ b/packages/electron-app/electron/main/process-manager.ts @@ -0,0 +1,371 @@ +import { spawn, type ChildProcess } from "child_process" +import { app } from "electron" +import { createRequire } from "module" +import { EventEmitter } from "events" +import { existsSync, readFileSync } from "fs" +import os from "os" +import path from "path" +import { buildUserShellCommand, getUserShellEnv, supportsUserShell } from "./user-shell" + +const nodeRequire = createRequire(import.meta.url) + + +type CliState = "starting" | "ready" | "error" | "stopped" +type ListeningMode = "local" | "all" + +export interface CliStatus { + state: CliState + pid?: number + port?: number + url?: string + error?: string +} + +export interface CliLogEntry { + stream: "stdout" | "stderr" + message: string +} + +interface StartOptions { + dev: boolean +} + +interface CliEntryResolution { + entry: string + runner: "node" | "tsx" + runnerPath?: string +} + +const DEFAULT_CONFIG_PATH = "~/.config/codenomad/config.json" + +function resolveConfigPath(configPath?: string): string { + const target = configPath && configPath.trim().length > 0 ? configPath : DEFAULT_CONFIG_PATH + if (target.startsWith("~/")) { + return path.join(os.homedir(), target.slice(2)) + } + return path.resolve(target) +} + +function resolveHostForMode(mode: ListeningMode): string { + return mode === "local" ? "127.0.0.1" : "0.0.0.0" +} + +function readListeningModeFromConfig(): ListeningMode { + try { + const configPath = resolveConfigPath(process.env.CLI_CONFIG) + if (!existsSync(configPath)) return "local" + const content = readFileSync(configPath, "utf-8") + const parsed = JSON.parse(content) + const mode = parsed?.preferences?.listeningMode + if (mode === "local" || mode === "all") { + return mode + } + } catch (error) { + console.warn("[cli] failed to read listening mode from config", error) + } + return "local" +} + +export declare interface CliProcessManager { + on(event: "status", listener: (status: CliStatus) => void): this + on(event: "ready", listener: (status: CliStatus) => void): this + on(event: "log", listener: (entry: CliLogEntry) => void): this + on(event: "exit", listener: (status: CliStatus) => void): this + on(event: "error", listener: (error: Error) => void): this +} + +export class CliProcessManager extends EventEmitter { + private child?: ChildProcess + private status: CliStatus = { state: "stopped" } + private stdoutBuffer = "" + private stderrBuffer = "" + private userEnv: Record = {} + + setUserEnv(env: Record) { + this.userEnv = { ...env } + } + + async start(options: StartOptions): Promise { + if (this.child) { + await this.stop() + } + + this.stdoutBuffer = "" + this.stderrBuffer = "" + this.updateStatus({ state: "starting", port: undefined, pid: undefined, url: undefined, error: undefined }) + + const cliEntry = this.resolveCliEntry(options) + const listeningMode = this.resolveListeningMode() + const host = resolveHostForMode(listeningMode) + const args = this.buildCliArgs(options, host) + + console.info( + `[cli] launching CodeNomad CLI (${options.dev ? "dev" : "prod"}) using ${cliEntry.runner} at ${cliEntry.entry} (host=${host})`, + ) + + const env = supportsUserShell() ? getUserShellEnv() : { ...process.env } + env.ELECTRON_RUN_AS_NODE = "1" + Object.assign(env, this.userEnv) + + const spawnDetails = supportsUserShell() + ? buildUserShellCommand(`ELECTRON_RUN_AS_NODE=1 exec ${this.buildCommand(cliEntry, args)}`) + : this.buildDirectSpawn(cliEntry, args) + + const child = spawn(spawnDetails.command, spawnDetails.args, { + cwd: process.cwd(), + stdio: ["ignore", "pipe", "pipe"], + env, + shell: false, + }) + + console.info(`[cli] spawn command: ${spawnDetails.command} ${spawnDetails.args.join(" ")}`) + if (!child.pid) { + console.error("[cli] spawn failed: no pid") + } + + this.child = child + this.updateStatus({ pid: child.pid ?? undefined }) + + child.stdout?.on("data", (data: Buffer) => { + this.handleStream(data.toString(), "stdout") + }) + + child.stderr?.on("data", (data: Buffer) => { + this.handleStream(data.toString(), "stderr") + }) + + child.on("error", (error) => { + console.error("[cli] failed to start CLI:", error) + this.updateStatus({ state: "error", error: error.message }) + this.emit("error", error) + }) + + child.on("exit", (code, signal) => { + const failed = this.status.state !== "ready" + const error = failed ? this.status.error ?? `CLI exited with code ${code ?? 0}${signal ? ` (${signal})` : ""}` : undefined + console.info(`[cli] exit (code=${code}, signal=${signal || ""})${error ? ` error=${error}` : ""}`) + this.updateStatus({ state: failed ? "error" : "stopped", error }) + if (failed && error) { + this.emit("error", new Error(error)) + } + this.emit("exit", this.status) + this.child = undefined + }) + + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + this.handleTimeout() + reject(new Error("CLI startup timeout")) + }, 60000) + + this.once("ready", (status) => { + clearTimeout(timeout) + resolve(status) + }) + + this.once("error", (error) => { + clearTimeout(timeout) + reject(error) + }) + }) + } + + async stop(): Promise { + const child = this.child + if (!child) { + this.updateStatus({ state: "stopped" }) + return + } + + return new Promise((resolve) => { + const killTimeout = setTimeout(() => { + child.kill("SIGKILL") + }, 4000) + + child.on("exit", () => { + clearTimeout(killTimeout) + this.child = undefined + console.info("[cli] CLI process exited") + this.updateStatus({ state: "stopped" }) + resolve() + }) + + child.kill("SIGTERM") + }) + } + + getStatus(): CliStatus { + return { ...this.status } + } + + private resolveListeningMode(): ListeningMode { + return readListeningModeFromConfig() + } + + private handleTimeout() { + if (this.child) { + this.child.kill("SIGKILL") + this.child = undefined + } + this.updateStatus({ state: "error", error: "CLI did not start in time" }) + this.emit("error", new Error("CLI did not start in time")) + } + + private handleStream(chunk: string, stream: "stdout" | "stderr") { + if (stream === "stdout") { + this.stdoutBuffer += chunk + this.processBuffer("stdout") + } else { + this.stderrBuffer += chunk + this.processBuffer("stderr") + } + } + + private processBuffer(stream: "stdout" | "stderr") { + const buffer = stream === "stdout" ? this.stdoutBuffer : this.stderrBuffer + const lines = buffer.split("\n") + const trailing = lines.pop() ?? "" + + if (stream === "stdout") { + this.stdoutBuffer = trailing + } else { + this.stderrBuffer = trailing + } + + for (const line of lines) { + if (!line.trim()) continue + console.info(`[cli][${stream}] ${line}`) + this.emit("log", { stream, message: line }) + + const port = this.extractPort(line) + if (port && this.status.state === "starting") { + const url = `http://127.0.0.1:${port}` + console.info(`[cli] ready on ${url}`) + this.updateStatus({ state: "ready", port, url }) + this.emit("ready", this.status) + } + } + } + + private extractPort(line: string): number | null { + const readyMatch = line.match(/CodeNomad Server is ready at http:\/\/[^:]+:(\d+)/i) + if (readyMatch) { + return parseInt(readyMatch[1], 10) + } + + if (line.toLowerCase().includes("http server listening")) { + const httpMatch = line.match(/:(\d{2,5})(?!.*:\d)/) + if (httpMatch) { + return parseInt(httpMatch[1], 10) + } + try { + const parsed = JSON.parse(line) + if (typeof parsed.port === "number") { + return parsed.port + } + } catch { + // not JSON, ignore + } + } + + return null + } + + private updateStatus(patch: Partial) { + this.status = { ...this.status, ...patch } + this.emit("status", this.status) + } + + private buildCliArgs(options: StartOptions, host: string): string[] { + const args = ["serve", "--host", host, "--port", "0"] + + if (options.dev) { + const uiPort = process.env.VITE_PORT || "3000" + args.push("--ui-dev-server", `http://localhost:${uiPort}`, "--log-level", "debug") + } + + return args + } + + private buildCommand(cliEntry: CliEntryResolution, args: string[]): string { + const parts = [JSON.stringify(process.execPath)] + if (cliEntry.runner === "tsx" && cliEntry.runnerPath) { + parts.push(JSON.stringify(cliEntry.runnerPath)) + } + parts.push(JSON.stringify(cliEntry.entry)) + args.forEach((arg) => parts.push(JSON.stringify(arg))) + return parts.join(" ") + } + + private buildDirectSpawn(cliEntry: CliEntryResolution, args: string[]) { + if (cliEntry.runner === "tsx") { + return { command: process.execPath, args: [cliEntry.runnerPath!, cliEntry.entry, ...args] } + } + + return { command: process.execPath, args: [cliEntry.entry, ...args] } + } + + private resolveCliEntry(options: StartOptions): CliEntryResolution { + if (options.dev) { + const tsxPath = this.resolveTsx() + if (!tsxPath) { + throw new Error("tsx is required to run the CLI in development mode. Please install dependencies.") + } + const devEntry = this.resolveDevEntry() + return { entry: devEntry, runner: "tsx", runnerPath: tsxPath } + } + + const distEntry = this.resolveProdEntry() + return { entry: distEntry, runner: "node" } + } + + private resolveTsx(): string | null { + const candidates: Array string)> = [ + () => nodeRequire.resolve("tsx/cli"), + () => nodeRequire.resolve("tsx/dist/cli.mjs"), + () => nodeRequire.resolve("tsx/dist/cli.cjs"), + path.resolve(process.cwd(), "node_modules", "tsx", "dist", "cli.mjs"), + path.resolve(process.cwd(), "node_modules", "tsx", "dist", "cli.cjs"), + path.resolve(process.cwd(), "..", "node_modules", "tsx", "dist", "cli.mjs"), + path.resolve(process.cwd(), "..", "node_modules", "tsx", "dist", "cli.cjs"), + path.resolve(process.cwd(), "..", "..", "node_modules", "tsx", "dist", "cli.mjs"), + path.resolve(process.cwd(), "..", "..", "node_modules", "tsx", "dist", "cli.cjs"), + path.resolve(app.getAppPath(), "..", "node_modules", "tsx", "dist", "cli.mjs"), + path.resolve(app.getAppPath(), "..", "node_modules", "tsx", "dist", "cli.cjs"), + ] + + for (const candidate of candidates) { + try { + const resolved = typeof candidate === "function" ? candidate() : candidate + if (resolved && existsSync(resolved)) { + return resolved + } + } catch { + continue + } + } + + return null + } + + private resolveDevEntry(): string { + const entry = path.resolve(process.cwd(), "..", "server", "src", "index.ts") + if (!existsSync(entry)) { + throw new Error(`Dev CLI entry not found at ${entry}. Run npm run dev:electron from the repository root after installing dependencies.`) + } + return entry + } + + private resolveProdEntry(): string { + try { + const entry = nodeRequire.resolve("@neuralnomads/codenomad/dist/bin.js") + if (existsSync(entry)) { + return entry + } + } catch { + // fall through to error below + } + throw new Error("Unable to locate CodeNomad CLI build (dist/bin.js). Run npm run build --workspace @neuralnomads/codenomad.") + } +} + diff --git a/packages/electron-app/electron/main/storage.ts b/packages/electron-app/electron/main/storage.ts new file mode 100644 index 0000000..a93cc19 --- /dev/null +++ b/packages/electron-app/electron/main/storage.ts @@ -0,0 +1,121 @@ +import { app, ipcMain } from "electron" +import { join } from "path" +import { readFile, writeFile, mkdir, unlink, stat } from "fs/promises" +import { existsSync } from "fs" + +const CONFIG_DIR = join(app.getPath("home"), ".config", "codenomad") +const CONFIG_FILE = join(CONFIG_DIR, "config.json") +const INSTANCES_DIR = join(CONFIG_DIR, "instances") + +// File watching for config changes +let configWatchers = new Set() +let configLastModified = 0 +let configCache: string | null = null + +async function ensureDirectories() { + try { + await mkdir(CONFIG_DIR, { recursive: true }) + await mkdir(INSTANCES_DIR, { recursive: true }) + } catch (error) { + console.error("Failed to create directories:", error) + } +} + +async function readConfigWithCache(): Promise { + try { + const stats = await stat(CONFIG_FILE) + const currentModified = stats.mtime.getTime() + + // If file hasn't been modified since last read, return cache + if (configCache && configLastModified >= currentModified) { + return configCache + } + + const content = await readFile(CONFIG_FILE, "utf-8") + configCache = content + configLastModified = currentModified + return content + } catch (error) { + // File doesn't exist or can't be read + configCache = null + configLastModified = 0 + throw error + } +} + +function invalidateConfigCache() { + configCache = null + configLastModified = 0 +} + +export function setupStorageIPC() { + ensureDirectories() + + ipcMain.handle("storage:getConfigPath", async () => CONFIG_FILE) + ipcMain.handle("storage:getInstancesDir", async () => INSTANCES_DIR) + + ipcMain.handle("storage:readConfigFile", async () => { + try { + return await readConfigWithCache() + } catch (error) { + // Return empty config if file doesn't exist + return JSON.stringify({ preferences: { showThinkingBlocks: false, thinkingBlocksExpansion: "expanded" }, recentFolders: [] }, null, 2) + } + }) + + ipcMain.handle("storage:writeConfigFile", async (_, content: string) => { + try { + await writeFile(CONFIG_FILE, content, "utf-8") + invalidateConfigCache() + + // Notify other renderer processes about config change + const windows = require("electron").BrowserWindow.getAllWindows() + windows.forEach((win: any) => { + if (win.webContents && !win.webContents.isDestroyed()) { + win.webContents.send("storage:configChanged") + } + }) + } catch (error) { + console.error("Failed to write config file:", error) + throw error + } + }) + + ipcMain.handle("storage:readInstanceFile", async (_, filename: string) => { + const instanceFile = join(INSTANCES_DIR, `${filename}.json`) + try { + return await readFile(instanceFile, "utf-8") + } catch (error) { + // Return empty instance data if file doesn't exist + return JSON.stringify({ messageHistory: [] }, null, 2) + } + }) + + ipcMain.handle("storage:writeInstanceFile", async (_, filename: string, content: string) => { + const instanceFile = join(INSTANCES_DIR, `${filename}.json`) + try { + await writeFile(instanceFile, content, "utf-8") + } catch (error) { + console.error(`Failed to write instance file for ${filename}:`, error) + throw error + } + }) + + ipcMain.handle("storage:deleteInstanceFile", async (_, filename: string) => { + const instanceFile = join(INSTANCES_DIR, `${filename}.json`) + try { + if (existsSync(instanceFile)) { + await unlink(instanceFile) + } + } catch (error) { + console.error(`Failed to delete instance file for ${filename}:`, error) + throw error + } + }) +} + +// Clean up on app quit +app.on("before-quit", () => { + configCache = null + configLastModified = 0 +}) diff --git a/packages/electron-app/electron/main/user-shell.ts b/packages/electron-app/electron/main/user-shell.ts new file mode 100644 index 0000000..ee49e7c --- /dev/null +++ b/packages/electron-app/electron/main/user-shell.ts @@ -0,0 +1,139 @@ +import { spawn, spawnSync } from "child_process" +import path from "path" + +interface ShellCommand { + command: string + args: string[] +} + +const isWindows = process.platform === "win32" + +function getDefaultShellPath(): string { + if (process.env.SHELL && process.env.SHELL.trim().length > 0) { + return process.env.SHELL + } + + if (process.platform === "darwin") { + return "/bin/zsh" + } + + return "/bin/bash" +} + +function wrapCommandForShell(command: string, shellPath: string): string { + const shellName = path.basename(shellPath) + + if (shellName.includes("bash")) { + return 'if [ -f ~/.bashrc ]; then source ~/.bashrc >/dev/null 2>&1; fi; ' + command + } + + if (shellName.includes("zsh")) { + return 'if [ -f ~/.zshrc ]; then source ~/.zshrc >/dev/null 2>&1; fi; ' + command + } + + return command +} + +function buildShellArgs(shellPath: string): string[] { + const shellName = path.basename(shellPath) + if (shellName.includes("zsh")) { + return ["-l", "-i", "-c"] + } + return ["-l", "-c"] +} + +function sanitizeShellEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + const cleaned = { ...env } + delete cleaned.npm_config_prefix + delete cleaned.NPM_CONFIG_PREFIX + return cleaned +} + +export function supportsUserShell(): boolean { + return !isWindows +} + +export function buildUserShellCommand(userCommand: string): ShellCommand { + if (!supportsUserShell()) { + throw new Error("User shell invocation is only supported on POSIX platforms") + } + + const shellPath = getDefaultShellPath() + const script = wrapCommandForShell(userCommand, shellPath) + const args = buildShellArgs(shellPath) + + return { + command: shellPath, + args: [...args, script], + } +} + +export function getUserShellEnv(): NodeJS.ProcessEnv { + if (!supportsUserShell()) { + throw new Error("User shell invocation is only supported on POSIX platforms") + } + return sanitizeShellEnv(process.env) +} + +export function runUserShellCommand(userCommand: string, timeoutMs = 5000): Promise { + if (!supportsUserShell()) { + return Promise.reject(new Error("User shell invocation is only supported on POSIX platforms")) + } + + const { command, args } = buildUserShellCommand(userCommand) + const env = getUserShellEnv() + + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + stdio: ["ignore", "pipe", "pipe"], + env, + }) + + let stdout = "" + let stderr = "" + + const timeout = setTimeout(() => { + child.kill("SIGTERM") + reject(new Error(`Shell command timed out after ${timeoutMs}ms`)) + }, timeoutMs) + + child.stdout?.on("data", (data) => { + stdout += data.toString() + }) + + child.stderr?.on("data", (data) => { + stderr += data.toString() + }) + + child.on("error", (error) => { + clearTimeout(timeout) + reject(error) + }) + + child.on("close", (code) => { + clearTimeout(timeout) + if (code === 0) { + resolve(stdout.trim()) + } else { + reject(new Error(stderr.trim() || `Shell command exited with code ${code}`)) + } + }) + }) +} + +export function runUserShellCommandSync(userCommand: string): string { + if (!supportsUserShell()) { + throw new Error("User shell invocation is only supported on POSIX platforms") + } + + const { command, args } = buildUserShellCommand(userCommand) + const env = getUserShellEnv() + const result = spawnSync(command, args, { encoding: "utf-8", env }) + + if (result.status !== 0) { + const stderr = (result.stderr || "").toString().trim() + throw new Error(stderr || "Shell command failed") + } + + return (result.stdout || "").toString().trim() +} diff --git a/packages/electron-app/electron/main/user-store.ts b/packages/electron-app/electron/main/user-store.ts new file mode 100644 index 0000000..2831d79 --- /dev/null +++ b/packages/electron-app/electron/main/user-store.ts @@ -0,0 +1,267 @@ +import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, cpSync } from "fs" +import os from "os" +import path from "path" +import crypto from "crypto" + +interface UserRecord { + id: string + name: string + salt?: string + passwordHash?: string + isGuest?: boolean + createdAt: string + updatedAt: string +} + +interface UserStoreState { + users: UserRecord[] + activeUserId?: string +} + +const CONFIG_ROOT = path.join(os.homedir(), ".config", "codenomad") +const USERS_FILE = path.join(CONFIG_ROOT, "users.json") +const USERS_ROOT = path.join(CONFIG_ROOT, "users") +const LEGACY_ROOT = CONFIG_ROOT +const LEGACY_INTEGRATIONS_ROOT = path.join(os.homedir(), ".nomadarch") + +function nowIso() { + return new Date().toISOString() +} + +function sanitizeId(value: string) { + return value + .toLowerCase() + .trim() + .replace(/[^a-z0-9-_]+/g, "-") + .replace(/-{2,}/g, "-") + .replace(/^-|-$/g, "") +} + +function hashPassword(password: string, salt: string) { + return crypto.pbkdf2Sync(password, salt, 120000, 32, "sha256").toString("base64") +} + +function generateSalt() { + return crypto.randomBytes(16).toString("base64") +} + +function ensureDir(dir: string) { + if (!existsSync(dir)) { + mkdirSync(dir, { recursive: true }) + } +} + +function readStore(): UserStoreState { + try { + if (!existsSync(USERS_FILE)) { + return { users: [] } + } + const content = readFileSync(USERS_FILE, "utf-8") + const parsed = JSON.parse(content) as UserStoreState + return { + users: Array.isArray(parsed.users) ? parsed.users : [], + activeUserId: parsed.activeUserId, + } + } catch { + return { users: [] } + } +} + +function writeStore(state: UserStoreState) { + ensureDir(CONFIG_ROOT) + ensureDir(USERS_ROOT) + writeFileSync(USERS_FILE, JSON.stringify(state, null, 2), "utf-8") +} + +function ensureUniqueId(base: string, existing: Set) { + let candidate = sanitizeId(base) || "user" + let index = 1 + while (existing.has(candidate)) { + candidate = `${candidate}-${index}` + index += 1 + } + return candidate +} + +function getUserDir(userId: string) { + return path.join(USERS_ROOT, userId) +} + +function migrateLegacyData(targetDir: string) { + const legacyConfig = path.join(LEGACY_ROOT, "config.json") + const legacyInstances = path.join(LEGACY_ROOT, "instances") + const legacyWorkspaces = path.join(LEGACY_ROOT, "opencode-workspaces") + + ensureDir(targetDir) + + if (existsSync(legacyConfig)) { + cpSync(legacyConfig, path.join(targetDir, "config.json"), { force: true }) + } + if (existsSync(legacyInstances)) { + cpSync(legacyInstances, path.join(targetDir, "instances"), { recursive: true, force: true }) + } + if (existsSync(legacyWorkspaces)) { + cpSync(legacyWorkspaces, path.join(targetDir, "opencode-workspaces"), { recursive: true, force: true }) + } + + if (existsSync(LEGACY_INTEGRATIONS_ROOT)) { + cpSync(LEGACY_INTEGRATIONS_ROOT, path.join(targetDir, "integrations"), { recursive: true, force: true }) + } +} + +export function ensureDefaultUsers(): UserRecord { + const store = readStore() + if (store.users.length > 0) { + const active = store.users.find((u) => u.id === store.activeUserId) ?? store.users[0] + if (!store.activeUserId) { + store.activeUserId = active.id + writeStore(store) + } + return active + } + + const existingIds = new Set() + const userId = ensureUniqueId("roman", existingIds) + const salt = generateSalt() + const passwordHash = hashPassword("q1w2e3r4", salt) + const record: UserRecord = { + id: userId, + name: "roman", + salt, + passwordHash, + createdAt: nowIso(), + updatedAt: nowIso(), + } + + store.users.push(record) + store.activeUserId = record.id + writeStore(store) + + const userDir = getUserDir(record.id) + migrateLegacyData(userDir) + + return record +} + +export function listUsers(): UserRecord[] { + return readStore().users +} + +export function getActiveUser(): UserRecord | null { + const store = readStore() + if (!store.activeUserId) return null + return store.users.find((user) => user.id === store.activeUserId) ?? null +} + +export function setActiveUser(userId: string) { + const store = readStore() + const user = store.users.find((u) => u.id === userId) + if (!user) { + throw new Error("User not found") + } + store.activeUserId = userId + writeStore(store) + return user +} + +export function createUser(name: string, password: string) { + const store = readStore() + const existingIds = new Set(store.users.map((u) => u.id)) + const id = ensureUniqueId(name, existingIds) + const salt = generateSalt() + const passwordHash = hashPassword(password, salt) + const record: UserRecord = { + id, + name, + salt, + passwordHash, + createdAt: nowIso(), + updatedAt: nowIso(), + } + store.users.push(record) + writeStore(store) + ensureDir(getUserDir(id)) + return record +} + +export function createGuestUser() { + const store = readStore() + const existingIds = new Set(store.users.map((u) => u.id)) + const id = ensureUniqueId(`guest-${crypto.randomUUID().slice(0, 8)}`, existingIds) + const record: UserRecord = { + id, + name: "Guest", + isGuest: true, + createdAt: nowIso(), + updatedAt: nowIso(), + } + store.users.push(record) + store.activeUserId = id + writeStore(store) + ensureDir(getUserDir(id)) + return record +} + +export function updateUser(userId: string, updates: { name?: string; password?: string }) { + const store = readStore() + const target = store.users.find((u) => u.id === userId) + if (!target) { + throw new Error("User not found") + } + if (updates.name) { + target.name = updates.name + } + if (updates.password && !target.isGuest) { + const salt = generateSalt() + target.salt = salt + target.passwordHash = hashPassword(updates.password, salt) + } + target.updatedAt = nowIso() + writeStore(store) + return target +} + +export function deleteUser(userId: string) { + const store = readStore() + const target = store.users.find((u) => u.id === userId) + if (!target) return + store.users = store.users.filter((u) => u.id !== userId) + if (store.activeUserId === userId) { + store.activeUserId = store.users[0]?.id + } + writeStore(store) + const dir = getUserDir(userId) + if (existsSync(dir)) { + rmSync(dir, { recursive: true, force: true }) + } +} + +export function verifyPassword(userId: string, password: string): boolean { + const store = readStore() + const user = store.users.find((u) => u.id === userId) + if (!user) return false + if (user.isGuest) return true + if (!user.salt || !user.passwordHash) return false + return hashPassword(password, user.salt) === user.passwordHash +} + +export function getUserDataRoot(userId: string) { + return getUserDir(userId) +} + +export function clearGuestUsers() { + const store = readStore() + const guests = store.users.filter((u) => u.isGuest) + if (guests.length === 0) return + store.users = store.users.filter((u) => !u.isGuest) + if (store.activeUserId && guests.some((u) => u.id === store.activeUserId)) { + store.activeUserId = store.users[0]?.id + } + writeStore(store) + for (const guest of guests) { + const dir = getUserDir(guest.id) + if (existsSync(dir)) { + rmSync(dir, { recursive: true, force: true }) + } + } +} diff --git a/packages/electron-app/electron/preload/index.cjs b/packages/electron-app/electron/preload/index.cjs new file mode 100644 index 0000000..40b04f6 --- /dev/null +++ b/packages/electron-app/electron/preload/index.cjs @@ -0,0 +1,24 @@ +const { contextBridge, ipcRenderer } = require("electron") + +const electronAPI = { + onCliStatus: (callback) => { + ipcRenderer.on("cli:status", (_, data) => callback(data)) + return () => ipcRenderer.removeAllListeners("cli:status") + }, + onCliError: (callback) => { + ipcRenderer.on("cli:error", (_, data) => callback(data)) + return () => ipcRenderer.removeAllListeners("cli:error") + }, + getCliStatus: () => ipcRenderer.invoke("cli:getStatus"), + restartCli: () => ipcRenderer.invoke("cli:restart"), + openDialog: (options) => ipcRenderer.invoke("dialog:open", options), + listUsers: () => ipcRenderer.invoke("users:list"), + getActiveUser: () => ipcRenderer.invoke("users:active"), + createUser: (payload) => ipcRenderer.invoke("users:create", payload), + updateUser: (payload) => ipcRenderer.invoke("users:update", payload), + deleteUser: (payload) => ipcRenderer.invoke("users:delete", payload), + createGuest: () => ipcRenderer.invoke("users:createGuest"), + loginUser: (payload) => ipcRenderer.invoke("users:login", payload), +} + +contextBridge.exposeInMainWorld("electronAPI", electronAPI) diff --git a/packages/electron-app/electron/resources/icon.icns b/packages/electron-app/electron/resources/icon.icns new file mode 100644 index 0000000..4431bb4 Binary files /dev/null and b/packages/electron-app/electron/resources/icon.icns differ diff --git a/packages/electron-app/electron/resources/icon.ico b/packages/electron-app/electron/resources/icon.ico new file mode 100644 index 0000000..7d50243 Binary files /dev/null and b/packages/electron-app/electron/resources/icon.ico differ diff --git a/packages/electron-app/electron/resources/icon.png b/packages/electron-app/electron/resources/icon.png new file mode 100644 index 0000000..4c08915 Binary files /dev/null and b/packages/electron-app/electron/resources/icon.png differ diff --git a/packages/electron-app/electron/tsconfig.json b/packages/electron-app/electron/tsconfig.json new file mode 100644 index 0000000..aa4873c --- /dev/null +++ b/packages/electron-app/electron/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["./**/*.ts", "./**/*.tsx"] +} diff --git a/packages/electron-app/package.json b/packages/electron-app/package.json new file mode 100644 index 0000000..40984d0 --- /dev/null +++ b/packages/electron-app/package.json @@ -0,0 +1,138 @@ +{ + "name": "@neuralnomads/codenomad-electron-app", + "version": "0.4.0", + "description": "CodeNomad - AI coding assistant", + "author": { + "name": "Neural Nomads", + "email": "codenomad@neuralnomads.ai" + }, + "type": "module", + "main": "dist/main/main.js", + "repository": { + "type": "git", + "url": "https://github.com/NeuralNomadsAI/CodeNomad.git" + }, + "homepage": "https://github.com/NeuralNomadsAI/CodeNomad", + "scripts": { + "dev": "electron-vite dev", + "dev:electron": "cross-env NODE_ENV=development ELECTRON_ENABLE_LOGGING=1 NODE_OPTIONS=\"--import tsx\" electron electron/main/main.ts", + "build": "electron-vite build", + "typecheck": "tsc --noEmit -p tsconfig.json", + "preview": "electron-vite preview", + "build:binaries": "node scripts/build.js", + "build:mac": "node scripts/build.js mac", + "build:mac-x64": "node scripts/build.js mac-x64", + "build:mac-arm64": "node scripts/build.js mac-arm64", + "build:win": "node scripts/build.js win", + "build:win-arm64": "node scripts/build.js win-arm64", + "build:linux": "node scripts/build.js linux", + "build:linux-arm64": "node scripts/build.js linux-arm64", + "build:linux-rpm": "node scripts/build.js linux-rpm", + "build:all": "node scripts/build.js all", + "package:mac": "electron-builder --mac", + "package:win": "electron-builder --win", + "package:linux": "electron-builder --linux" + }, + "dependencies": { + "@neuralnomads/codenomad": "file:../server", + "@codenomad/ui": "file:../ui" + }, + "devDependencies": { + "7zip-bin": "^5.2.0", + "app-builder-bin": "^4.2.0", + "cross-env": "^7.0.3", + "electron": "39.0.0", + "electron-builder": "^24.0.0", + "electron-vite": "4.0.1", + "png2icons": "^2.0.1", + "pngjs": "^7.0.0", + "tsx": "^4.20.6", + "typescript": "^5.3.0", + "vite": "^5.0.0", + "vite-plugin-solid": "^2.10.0" + }, + "build": { + "appId": "ai.opencode.client", + "productName": "CodeNomad", + "directories": { + "output": "release", + "buildResources": "electron/resources" + }, + "files": [ + "dist/**/*", + "package.json" + ], + "extraResources": [ + { + "from": "electron/resources", + "to": "", + "filter": [ + "!icon.icns", + "!icon.ico" + ] + } + ], + "mac": { + "category": "public.app-category.developer-tools", + "target": [ + { + "target": "zip", + "arch": [ + "x64", + "arm64" + ] + } + ], + "artifactName": "CodeNomad-${version}-${os}-${arch}.${ext}", + "icon": "electron/resources/icon.icns" + }, + "dmg": { + "contents": [ + { + "x": 130, + "y": 220 + }, + { + "x": 410, + "y": 220, + "type": "link", + "path": "/Applications" + } + ] + }, + "win": { + "target": [ + { + "target": "zip", + "arch": [ + "x64", + "arm64" + ] + } + ], + "artifactName": "CodeNomad-${version}-${os}-${arch}.${ext}", + "icon": "electron/resources/icon.ico" + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true + }, + "linux": { + "target": [ + { + "target": "zip", + "arch": [ + "x64", + "arm64" + ] + } + ], + "artifactName": "CodeNomad-${version}-${os}-${arch}.${ext}", + "category": "Development", + "icon": "electron/resources/icon.png" + } + }, + "private": true +} diff --git a/packages/electron-app/scripts/build.js b/packages/electron-app/scripts/build.js new file mode 100644 index 0000000..17f58d0 --- /dev/null +++ b/packages/electron-app/scripts/build.js @@ -0,0 +1,131 @@ +#!/usr/bin/env node + +import { spawn } from "child_process" +import { existsSync } from "fs" +import { join } from "path" +import { fileURLToPath } from "url" + +const __dirname = fileURLToPath(new URL(".", import.meta.url)) +const appDir = join(__dirname, "..") +const workspaceRoot = join(appDir, "..", "..") + +const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm" +const npxCmd = process.platform === "win32" ? "npx.cmd" : "npx" +const nodeModulesPath = join(appDir, "node_modules") +const workspaceNodeModulesPath = join(workspaceRoot, "node_modules") + +const platforms = { + mac: { + args: ["--mac", "--x64", "--arm64"], + description: "macOS (Intel & Apple Silicon)", + }, + "mac-x64": { + args: ["--mac", "--x64"], + description: "macOS (Intel only)", + }, + "mac-arm64": { + args: ["--mac", "--arm64"], + description: "macOS (Apple Silicon only)", + }, + win: { + args: ["--win", "--x64"], + description: "Windows (x64)", + }, + "win-arm64": { + args: ["--win", "--arm64"], + description: "Windows (ARM64)", + }, + linux: { + args: ["--linux", "--x64"], + description: "Linux (x64)", + }, + "linux-arm64": { + args: ["--linux", "--arm64"], + description: "Linux (ARM64)", + }, + "linux-rpm": { + args: ["--linux", "rpm", "--x64", "--arm64"], + description: "Linux RPM packages (x64 & ARM64)", + }, + all: { + args: ["--mac", "--win", "--linux", "--x64", "--arm64"], + description: "All platforms (macOS, Windows, Linux)", + }, +} + +function run(command, args, options = {}) { + return new Promise((resolve, reject) => { + const spawnOptions = { + cwd: appDir, + stdio: "inherit", + shell: process.platform === "win32", + ...options, + env: { ...process.env, NODE_PATH: nodeModulesPath, ...(options.env || {}) }, + } + + const child = spawn(command, args, spawnOptions) + + child.on("error", reject) + child.on("exit", (code) => { + if (code === 0) { + resolve(undefined) + } else { + reject(new Error(`${command} ${args.join(" ")} exited with code ${code}`)) + } + }) + }) +} + +function printAvailablePlatforms() { + console.error(`\nAvailable platforms:`) + for (const [name, cfg] of Object.entries(platforms)) { + console.error(` - ${name.padEnd(12)} : ${cfg.description}`) + } +} + +async function build(platform) { + const config = platforms[platform] + + if (!config) { + console.error(`❌ Unknown platform: ${platform}`) + printAvailablePlatforms() + process.exit(1) + } + + console.log(`\n🔨 Building for: ${config.description}\n`) + + try { + console.log("📦 Step 1/3: Building CLI dependency...\n") + await run(npmCmd, ["run", "build", "--workspace", "@neuralnomads/codenomad"], { + cwd: workspaceRoot, + env: { NODE_PATH: workspaceNodeModulesPath }, + }) + + console.log("\n📦 Step 2/3: Building Electron app...\n") + await run(npmCmd, ["run", "build"]) + + console.log("\n📦 Step 3/3: Packaging binaries...\n") + const distPath = join(appDir, "dist") + if (!existsSync(distPath)) { + throw new Error("dist/ directory not found. Build failed.") + } + + await run(npxCmd, ["electron-builder", "--publish=never", ...config.args]) + + console.log("\n✅ Build complete!") + console.log(`📁 Binaries available in: ${join(appDir, "release")}\n`) + } catch (error) { + console.error("\n❌ Build failed:", error) + process.exit(1) + } +} + +const platform = process.argv[2] || "mac" + +console.log(` +╔════════════════════════════════════════╗ +║ CodeNomad - Binary Builder ║ +╚════════════════════════════════════════╝ +`) + +await build(platform) diff --git a/packages/electron-app/scripts/dev.sh b/packages/electron-app/scripts/dev.sh new file mode 100644 index 0000000..acab779 --- /dev/null +++ b/packages/electron-app/scripts/dev.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if ! command -v node >/dev/null 2>&1; then + echo "Node.js is required to run the development environment." >&2 + exit 1 +fi + +# Resolve the Electron binary via Node to avoid Bun resolution hiccups +ELECTRON_EXEC_PATH="$(node -p "require('electron')")" + +if [[ -z "${ELECTRON_EXEC_PATH}" ]]; then + echo "Failed to resolve the Electron binary path." >&2 + exit 1 +fi + +export NODE_ENV="${NODE_ENV:-development}" +export ELECTRON_EXEC_PATH + +# ELECTRON_VITE_BIN="$ROOT_DIR/node_modules/.bin/electron-vite" + +if [[ ! -x "${ELECTRON_VITE_BIN}" ]]; then + echo "electron-vite binary not found. Have you installed dependencies?" >&2 + exit 1 +fi + +exec "${ELECTRON_VITE_BIN}" dev "$@" diff --git a/packages/electron-app/scripts/generate-icons.js b/packages/electron-app/scripts/generate-icons.js new file mode 100644 index 0000000..c049ef6 --- /dev/null +++ b/packages/electron-app/scripts/generate-icons.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +import { mkdirSync, readFileSync, writeFileSync } from "fs" +import { resolve, join, basename } from "path" +import { PNG } from "pngjs" +import png2icons from "png2icons" + +function printUsage() { + console.log(`\nUsage: node scripts/generate-icons.js [outputDir] [--name icon] [--radius 0.22]\n\nOptions:\n --name Base filename for generated assets (default: icon)\n --radius Corner radius ratio between 0 and 0.5 (default: 0.22)\n --help Show this message\n`) +} + +function parseArgs(argv) { + const args = [...argv] + const options = { + name: "icon", + radius: 0.22, + } + + for (let i = 0; i < args.length; i++) { + const token = args[i] + if (token === "--help" || token === "-h") { + options.help = true + continue + } + if (token === "--name" && i + 1 < args.length) { + options.name = args[i + 1] + i++ + continue + } + if (token === "--radius" && i + 1 < args.length) { + options.radius = Number(args[i + 1]) + i++ + continue + } + if (!options.input) { + options.input = token + continue + } + if (!options.output) { + options.output = token + continue + } + } + + return options +} + +function applyRoundedCorners(png, ratio) { + const { width, height, data } = png + const clamped = Math.max(0, Math.min(ratio, 0.5)) + if (clamped === 0) return png + + const radius = Math.max(1, Math.min(width, height) * clamped) + const radiusSq = radius * radius + const rightThreshold = width - radius + const bottomThreshold = height - radius + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (width * y + x) * 4 + if (data[idx + 3] === 0) continue + + const px = x + 0.5 + const py = y + 0.5 + + const inLeft = px < radius + const inRight = px > rightThreshold + const inTop = py < radius + const inBottom = py > bottomThreshold + + let outside = false + + if (inLeft && inTop) { + outside = (px - radius) ** 2 + (py - radius) ** 2 > radiusSq + } else if (inRight && inTop) { + outside = (px - rightThreshold) ** 2 + (py - radius) ** 2 > radiusSq + } else if (inLeft && inBottom) { + outside = (px - radius) ** 2 + (py - bottomThreshold) ** 2 > radiusSq + } else if (inRight && inBottom) { + outside = (px - rightThreshold) ** 2 + (py - bottomThreshold) ** 2 > radiusSq + } + + if (outside) { + data[idx + 3] = 0 + } + } + } + + return png +} + +async function main() { + const args = parseArgs(process.argv.slice(2)) + + if (args.help || !args.input) { + printUsage() + process.exit(args.help ? 0 : 1) + } + + const inputPath = resolve(args.input) + const outputDir = resolve(args.output || "electron/resources") + const baseName = args.name || basename(inputPath, ".png") + const radiusRatio = Number.isFinite(args.radius) ? args.radius : 0.22 + + let buffer + try { + buffer = readFileSync(inputPath) + } catch (error) { + console.error(`Failed to read ${inputPath}:`, error.message) + process.exit(1) + } + + let png + try { + png = PNG.sync.read(buffer) + } catch (error) { + console.error("Input must be a valid PNG:", error.message) + process.exit(1) + } + + applyRoundedCorners(png, radiusRatio) + + const roundedBuffer = PNG.sync.write(png) + + try { + mkdirSync(outputDir, { recursive: true }) + } catch (error) { + console.error("Failed to create output directory:", error.message) + process.exit(1) + } + + const pngPath = join(outputDir, `${baseName}.png`) + writeFileSync(pngPath, roundedBuffer) + + const icns = png2icons.createICNS(roundedBuffer, png2icons.BICUBIC, false) + if (!icns) { + console.error("Failed to create ICNS file. Make sure the source PNG is at least 256x256.") + process.exit(1) + } + writeFileSync(join(outputDir, `${baseName}.icns`), icns) + + const ico = png2icons.createICO(roundedBuffer, png2icons.BICUBIC, false) + if (!ico) { + console.error("Failed to create ICO file. Make sure the source PNG is at least 256x256.") + process.exit(1) + } + writeFileSync(join(outputDir, `${baseName}.ico`), ico) + + console.log(`\nGenerated assets in ${outputDir}:`) + console.log(`- ${baseName}.png`) + console.log(`- ${baseName}.icns`) + console.log(`- ${baseName}.ico`) +} + +main() diff --git a/packages/electron-app/tsconfig.json b/packages/electron-app/tsconfig.json new file mode 100644 index 0000000..af51792 --- /dev/null +++ b/packages/electron-app/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["ES2020"], + "moduleResolution": "bundler", + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["electron/**/*.ts", "electron.vite.config.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/opencode-config/opencode.jsonc b/packages/opencode-config/opencode.jsonc new file mode 100644 index 0000000..c3eb6a5 --- /dev/null +++ b/packages/opencode-config/opencode.jsonc @@ -0,0 +1,3 @@ +{ + "$schema": "https://opencode.ai/config.json" +} \ No newline at end of file diff --git a/packages/opencode-config/plugin/hello.js b/packages/opencode-config/plugin/hello.js new file mode 100644 index 0000000..6a6dc9d --- /dev/null +++ b/packages/opencode-config/plugin/hello.js @@ -0,0 +1,8 @@ +// NomadArch Plugin Template +// This file is a placeholder. OpenCode plugins are optional. +// To create a plugin, see: https://opencode.ai/docs/plugins + +export async function init() { + // No-op placeholder - customize as needed + return {} +} diff --git a/packages/server/.gitignore b/packages/server/.gitignore new file mode 100644 index 0000000..364fdec --- /dev/null +++ b/packages/server/.gitignore @@ -0,0 +1 @@ +public/ diff --git a/packages/server/.npmignore b/packages/server/.npmignore new file mode 100644 index 0000000..d50860b --- /dev/null +++ b/packages/server/.npmignore @@ -0,0 +1,5 @@ +node_modules +scripts/ +src/ +tsconfig.json +*.tsbuildinfo diff --git a/packages/server/README.md b/packages/server/README.md new file mode 100644 index 0000000..0649aa2 --- /dev/null +++ b/packages/server/README.md @@ -0,0 +1,58 @@ +# CodeNomad Server + +**CodeNomad Server** is the high-performance engine behind the CodeNomad cockpit. It transforms your machine into a robust development host, managing the lifecycle of multiple OpenCode instances and providing the low-latency data streams that long-haul builders demand. It bridges your local filesystem with the UI, ensuring that whether you are on localhost or a remote tunnel, you have the speed, clarity, and control of a native workspace. + +## Features & Capabilities + +### 🌍 Deployment Freedom +- **Remote Access**: Host CodeNomad on a powerful workstation and access it from your lightweight laptop. +- **Code Anywhere**: Tunnel in via VPN or SSH to code securely from coffee shops or while traveling. +- **Multi-Device**: The responsive web client works on tablets and iPads, turning any screen into a dev terminal. +- **Always-On**: Run as a background service so your sessions are always ready when you connect. + +### ⚡️ Workspace Power +- **Multi-Instance**: Juggle multiple OpenCode sessions side-by-side with per-instance tabs. +- **Long-Context Native**: Scroll through massive transcripts without hitches. +- **Deep Task Awareness**: Monitor background tasks and child sessions without losing your flow. +- **Command Palette**: A single, global palette to jump tabs, launch tools, and fire shortcuts. + +## Prerequisites +- **OpenCode**: `opencode` must be installed and configured on your system. +- Node.js 18+ and npm (for running or building from source). +- A workspace folder on disk you want to serve. +- Optional: a Chromium-based browser if you want `--launch` to open the UI automatically. + +## Usage + +### Run via npx (Recommended) +You can run CodeNomad directly without installing it: + +```sh +npx @neuralnomads/codenomad --launch +``` + +### Install Globally +Or install it globally to use the `codenomad` command: + +```sh +npm install -g @neuralnomads/codenomad +codenomad --launch +``` + +### Common Flags +You can configure the server using flags or environment variables: + +| Flag | Env Variable | Description | +|------|--------------|-------------| +| `--port ` | `CLI_PORT` | HTTP port (default 9898) | +| `--host ` | `CLI_HOST` | Interface to bind (default 127.0.0.1) | +| `--workspace-root ` | `CLI_WORKSPACE_ROOT` | Default root for new workspaces | +| `--unrestricted-root` | `CLI_UNRESTRICTED_ROOT` | Allow full-filesystem browsing | +| `--config ` | `CLI_CONFIG` | Config file location | +| `--launch` | `CLI_LAUNCH` | Open the UI in a Chromium-based browser | +| `--log-level ` | `CLI_LOG_LEVEL` | Logging level (trace, debug, info, warn, error) | + +### Data Storage +- **Config**: `~/.config/codenomad/config.json` +- **Instance Data**: `~/.config/codenomad/instances` (chat history, etc.) + diff --git a/packages/server/package-lock.json b/packages/server/package-lock.json new file mode 100644 index 0000000..ca16fa3 --- /dev/null +++ b/packages/server/package-lock.json @@ -0,0 +1,1333 @@ +{ + "name": "@neuralnomads/codenomad", + "version": "0.4.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@neuralnomads/codenomad", + "version": "0.4.0", + "dependencies": { + "@fastify/cors": "^8.5.0", + "commander": "^12.1.0", + "fastify": "^4.28.1", + "pino": "^9.4.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "ts-node": "^10.9.2", + "tsx": "^4.20.6", + "typescript": "^5.6.3" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/ajv-compiler": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-3.6.0.tgz", + "integrity": "sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", + "fast-uri": "^2.0.0" + } + }, + "node_modules/@fastify/cors": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@fastify/cors/-/cors-8.5.0.tgz", + "integrity": "sha512-/oZ1QSb02XjP0IK1U0IXktEsw/dUBTxJOW7IpIeO8c/tNalw/KjoNSJv1Sf6eqoBPO+TDGkifq6ynFK3v68HFQ==", + "license": "MIT", + "dependencies": { + "fastify-plugin": "^4.0.0", + "mnemonist": "0.39.6" + } + }, + "node_modules/@fastify/error": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-3.4.1.tgz", + "integrity": "sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==", + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-4.3.0.tgz", + "integrity": "sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==", + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^5.7.0" + } + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.1.1.tgz", + "integrity": "sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "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/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.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "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": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv/node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-8.4.0.tgz", + "integrity": "sha512-CDSwaxINFy59iNwhYnkvALBwZiTydGkOecZyPkqBpABYR1KqGEsET0VOOYDwtleZSUIdeY36DC2bSZ24CO1igA==", + "license": "MIT", + "dependencies": { + "@fastify/error": "^3.3.0", + "fastq": "^1.17.1" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fast-content-type-parse": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-1.1.0.tgz", + "integrity": "sha512-fBHHqSTFLVnR61C+gltJuE5GkVQMV0S2nqUO8TJ+5Z3qAKG8vAx4FKai1s5jq/inV1+sREynIWSuQ6HgoSXpDQ==", + "license": "MIT" + }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stringify": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-5.16.1.tgz", + "integrity": "sha512-KAdnLvy1yu/XrRtP+LJnxbBGrhN+xXu+gt3EUvZhYGKCr3lFHq/7UFJHHFgmJKoqlh6B40bZLEv7w46B0mqn1g==", + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.1.0", + "ajv": "^8.10.0", + "ajv-formats": "^3.0.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^2.1.0", + "json-schema-ref-resolver": "^1.0.1", + "rfdc": "^1.2.0" + } + }, + "node_modules/fast-json-stringify/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-2.4.0.tgz", + "integrity": "sha512-ypuAmmMKInk5q7XcepxlnUWDLWv4GFtaJqAzWKqn62IpQ3pejtr5dTVbt3vwqVaMKmkNR55sTT+CqUKIaT21BA==", + "license": "MIT" + }, + "node_modules/fastify": { + "version": "4.29.1", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-4.29.1.tgz", + "integrity": "sha512-m2kMNHIG92tSNWv+Z3UeTR9AWLLuo7KctC7mlFPtMEVrfjIhmQhkQnT9v15qA/BfVq3vvj134Y0jl9SBje3jXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^3.5.0", + "@fastify/error": "^3.4.0", + "@fastify/fast-json-stringify-compiler": "^4.3.0", + "abstract-logging": "^2.0.1", + "avvio": "^8.3.0", + "fast-content-type-parse": "^1.1.0", + "fast-json-stringify": "^5.8.0", + "find-my-way": "^8.0.0", + "light-my-request": "^5.11.0", + "pino": "^9.0.0", + "process-warning": "^3.0.0", + "proxy-addr": "^2.0.7", + "rfdc": "^1.3.0", + "secure-json-parse": "^2.7.0", + "semver": "^7.5.4", + "toad-cache": "^3.3.0" + } + }, + "node_modules/fastify-plugin": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-4.5.1.tgz", + "integrity": "sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/find-my-way": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-8.2.2.tgz", + "integrity": "sha512-Dobi7gcTEq8yszimcfp/R7+owiT4WncAJ7VTTgFH1jYJ5GaG1FbhjwDG820hptN0QDFvzVY3RfCzdInvGPGzjA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^3.1.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/json-schema-ref-resolver": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-1.0.1.tgz", + "integrity": "sha512-EJAj1pgHc1hxF6vo2Z3s69fMjO1INq6eGHXZ8Z6wCQeldCuwxGK9Sxf4/cScGn3FZubCVUehfWtcDM/PLteCQw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/light-my-request": { + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-5.14.0.tgz", + "integrity": "sha512-aORPWntbpH5esaYpGOOmri0OHDOe3wC5M2MQxZ9dvMLZm6DnaAn0kJlcbU9hwsQgLzmZyReKwFwwPkR+nHu5kA==", + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^0.7.0", + "process-warning": "^3.0.0", + "set-cookie-parser": "^2.4.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/mnemonist": { + "version": "0.39.6", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.6.tgz", + "integrity": "sha512-A/0v5Z59y63US00cRSLiloEIw3t5G+MiKz4BhX21FI+YBJXBOGW0ohFxTxO08dsOYlzxo87T7vGfZKYp2bcAWA==", + "license": "MIT", + "dependencies": { + "obliterator": "^2.0.1" + } + }, + "node_modules/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", + "license": "MIT" + }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pino": { + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", + "integrity": "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^3.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz", + "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", + "license": "MIT" + }, + "node_modules/pino/node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/process-warning": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz", + "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==", + "license": "MIT" + }, + "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==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/ret": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.4.3.tgz", + "integrity": "sha512-0f4Memo5QP7WQyUEAYUO3esD/XjOc3Zjjg5CPsAq1p8sIu0XPeMbHJemKA0BO7tV0X7+A0FoEpbmHXWxPyD3wQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/safe-regex2": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-3.1.0.tgz", + "integrity": "sha512-RAAZAGbap2kBfbVhvmnTFv73NWLMvDGOITFYTZBAaY8eR+Ir4ef7Up/e7amo+y1+AH+3PtLkrt9mvcTsG9LXug==", + "license": "MIT", + "dependencies": { + "ret": "~0.4.0" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/thread-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz", + "integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==", + "license": "MIT", + "dependencies": { + "real-require": "^0.2.0" + } + }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tsx": { + "version": "4.20.6", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.6.tgz", + "integrity": "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "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", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/packages/server/package.json b/packages/server/package.json new file mode 100644 index 0000000..e909523 --- /dev/null +++ b/packages/server/package.json @@ -0,0 +1,44 @@ +{ + "name": "@neuralnomads/codenomad", + "version": "0.4.0", + "description": "CodeNomad Server", + "author": { + "name": "Neural Nomads", + "email": "codenomad@neuralnomads.ai" + }, + "repository": { + "type": "git", + "url": "https://github.com/NeuralNomadsAI/CodeNomad.git" + }, + "type": "module", + "main": "dist/index.js", + "bin": { + "codenomad": "dist/bin.js" + }, + "scripts": { + "build": "npm run build:ui && npm run prepare-ui && tsc -p tsconfig.json && npm run prepare-config", + "build:ui": "npm run build --prefix ../ui", + "prepare-ui": "node ./scripts/copy-ui-dist.mjs", + "prepare-config": "node ./scripts/copy-opencode-config.mjs", + "dev": "cross-env CODENOMAD_DEV=1 CLI_UI_DEV_SERVER=http://localhost:3000 tsx src/index.ts", + "typecheck": "tsc --noEmit -p tsconfig.json" + }, + "dependencies": { + "@fastify/cors": "^8.5.0", + "@fastify/reply-from": "^9.8.0", + "@fastify/static": "^7.0.4", + "commander": "^12.1.0", + "fastify": "^4.28.1", + "fuzzysort": "^2.0.4", + "pino": "^9.4.0", + "ulid": "^3.0.2", + "undici": "^6.19.8", + "zod": "^3.23.8" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "ts-node": "^10.9.2", + "tsx": "^4.20.6", + "typescript": "^5.6.3" + } +} diff --git a/packages/server/scripts/copy-opencode-config.mjs b/packages/server/scripts/copy-opencode-config.mjs new file mode 100644 index 0000000..b63b862 --- /dev/null +++ b/packages/server/scripts/copy-opencode-config.mjs @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import { cpSync, existsSync, mkdirSync, rmSync } from "fs" +import path from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const cliRoot = path.resolve(__dirname, "..") +const sourceDir = path.resolve(cliRoot, "../opencode-config") +const targetDir = path.resolve(cliRoot, "dist/opencode-config") + +if (!existsSync(sourceDir)) { + console.error(`[copy-opencode-config] Missing source directory at ${sourceDir}`) + process.exit(1) +} + +rmSync(targetDir, { recursive: true, force: true }) +mkdirSync(path.dirname(targetDir), { recursive: true }) +cpSync(sourceDir, targetDir, { recursive: true }) + +console.log(`[copy-opencode-config] Copied ${sourceDir} -> ${targetDir}`) diff --git a/packages/server/scripts/copy-ui-dist.mjs b/packages/server/scripts/copy-ui-dist.mjs new file mode 100644 index 0000000..fdbe481 --- /dev/null +++ b/packages/server/scripts/copy-ui-dist.mjs @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import { cpSync, existsSync, mkdirSync, rmSync } from "fs" +import path from "path" +import { fileURLToPath } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const cliRoot = path.resolve(__dirname, "..") +const uiDistDir = path.resolve(cliRoot, "../ui/src/renderer/dist") +const targetDir = path.resolve(cliRoot, "public") + +if (!existsSync(uiDistDir)) { + console.error(`[copy-ui-dist] Expected UI build artifacts at ${uiDistDir}. Run the UI build before bundling the CLI.`) + process.exit(1) +} + +rmSync(targetDir, { recursive: true, force: true }) +mkdirSync(targetDir, { recursive: true }) +cpSync(uiDistDir, targetDir, { recursive: true }) + +console.log(`[copy-ui-dist] Copied UI bundle from ${uiDistDir} -> ${targetDir}`) diff --git a/packages/server/src/api-types.ts b/packages/server/src/api-types.ts new file mode 100644 index 0000000..1703af7 --- /dev/null +++ b/packages/server/src/api-types.ts @@ -0,0 +1,318 @@ +import type { + AgentModelSelection, + AgentModelSelections, + ConfigFile, + ModelPreference, + OpenCodeBinary, + Preferences, + RecentFolder, +} from "./config/schema" + +export type TaskStatus = "completed" | "interrupted" | "in-progress" | "pending" + +export interface Task { + id: string + title: string + status: TaskStatus + timestamp: number + messageIds?: string[] // IDs of messages associated with this task +} + +export interface SessionTasks { + [sessionId: string]: Task[] +} + +export interface SkillSelection { + id: string + name: string + description?: string +} + +export interface SkillDescriptor { + id: string + name: string + description?: string +} + +export interface SkillDetail extends SkillDescriptor { + content: string +} + +export interface SkillCatalogResponse { + skills: SkillDescriptor[] +} + +/** + * Canonical HTTP/SSE contract for the CLI server. + * These types are consumed by both the CLI implementation and any UI clients. + */ + +export type WorkspaceStatus = "starting" | "ready" | "stopped" | "error" + +export interface WorkspaceDescriptor { + id: string + /** Absolute path on the server host. */ + path: string + name?: string + status: WorkspaceStatus + /** PID/port are populated when the workspace is running. */ + pid?: number + port?: number + /** Canonical proxy path the CLI exposes for this instance. */ + proxyPath: string + /** Identifier of the binary resolved from config. */ + binaryId: string + binaryLabel: string + binaryVersion?: string + createdAt: string + updatedAt: string + /** Present when `status` is "error". */ + error?: string +} + +export interface WorkspaceCreateRequest { + path: string + name?: string +} + +export type WorkspaceCreateResponse = WorkspaceDescriptor +export type WorkspaceListResponse = WorkspaceDescriptor[] +export type WorkspaceDetailResponse = WorkspaceDescriptor + +export interface WorkspaceExportRequest { + destination: string + includeConfig?: boolean +} + +export interface WorkspaceExportResponse { + destination: string +} + +export interface WorkspaceImportRequest { + source: string + destination: string + includeConfig?: boolean +} + +export type WorkspaceImportResponse = WorkspaceDescriptor + +export interface WorkspaceMcpConfig { + mcpServers?: Record +} + +export interface WorkspaceMcpConfigResponse { + path: string + exists: boolean + config: WorkspaceMcpConfig +} + +export interface WorkspaceMcpConfigRequest { + config: WorkspaceMcpConfig +} + +export interface WorkspaceDeleteResponse { + id: string + status: WorkspaceStatus +} + +export type LogLevel = "debug" | "info" | "warn" | "error" + +export interface WorkspaceLogEntry { + workspaceId: string + timestamp: string + level: LogLevel + message: string +} + +export interface FileSystemEntry { + name: string + /** Path relative to the CLI server root ("." represents the root itself). */ + path: string + /** Absolute path when available (unrestricted listings). */ + absolutePath?: string + type: "file" | "directory" + size?: number + /** ISO timestamp of last modification when available. */ + modifiedAt?: string +} + +export type FileSystemScope = "restricted" | "unrestricted" +export type FileSystemPathKind = "relative" | "absolute" | "drives" + +export interface FileSystemListingMetadata { + scope: FileSystemScope + /** Canonical identifier of the current view ("." for restricted roots, absolute paths otherwise). */ + currentPath: string + /** Optional parent path if navigation upward is allowed. */ + parentPath?: string + /** Absolute path representing the root or origin point for this listing. */ + rootPath: string + /** Absolute home directory of the CLI host (useful defaults for unrestricted mode). */ + homePath: string + /** Human-friendly label for the current path. */ + displayPath: string + /** Indicates whether entry paths are relative, absolute, or represent drive roots. */ + pathKind: FileSystemPathKind +} + +export interface FileSystemListResponse { + entries: FileSystemEntry[] + metadata: FileSystemListingMetadata +} + +export const WINDOWS_DRIVES_ROOT = "__drives__" + +export interface WorkspaceFileResponse { + workspaceId: string + relativePath: string + /** UTF-8 file contents; binary files should be base64 encoded by the caller. */ + contents: string +} + +export type WorkspaceFileSearchResponse = FileSystemEntry[] + +export interface WorkspaceGitStatusEntry { + path: string + status: string +} + +export interface WorkspaceGitStatus { + isRepo: boolean + branch: string | null + ahead: number + behind: number + changes: WorkspaceGitStatusEntry[] + error?: string +} + +export interface InstanceData { + messageHistory: string[] + agentModelSelections: AgentModelSelection + sessionTasks?: SessionTasks // Multi-task chat support: tasks per session + sessionSkills?: Record // Selected skills per session + customAgents?: Array<{ + name: string + description?: string + prompt: string + }> +} + +export type InstanceStreamStatus = "connecting" | "connected" | "error" | "disconnected" + +export interface InstanceStreamEvent { + type: string + properties?: Record + [key: string]: unknown +} + +export interface BinaryRecord { + id: string + path: string + label: string + version?: string + + /** Indicates that this binary will be picked when workspaces omit an explicit choice. */ + isDefault: boolean + lastValidatedAt?: string + validationError?: string +} + +export type AppConfig = ConfigFile +export type AppConfigResponse = AppConfig +export type AppConfigUpdateRequest = Partial + +export interface BinaryListResponse { + binaries: BinaryRecord[] +} + +export interface BinaryCreateRequest { + path: string + label?: string + makeDefault?: boolean +} + +export interface BinaryUpdateRequest { + label?: string + makeDefault?: boolean +} + +export interface BinaryValidationResult { + valid: boolean + version?: string + error?: string +} + +export type WorkspaceEventType = + | "workspace.created" + | "workspace.started" + | "workspace.error" + | "workspace.stopped" + | "workspace.log" + | "config.appChanged" + | "config.binariesChanged" + | "instance.dataChanged" + | "instance.event" + | "instance.eventStatus" + | "app.releaseAvailable" + +export type WorkspaceEventPayload = + | { type: "workspace.created"; workspace: WorkspaceDescriptor } + | { type: "workspace.started"; workspace: WorkspaceDescriptor } + | { type: "workspace.error"; workspace: WorkspaceDescriptor } + | { type: "workspace.stopped"; workspaceId: string } + | { type: "workspace.log"; entry: WorkspaceLogEntry } + | { type: "config.appChanged"; config: AppConfig } + | { type: "config.binariesChanged"; binaries: BinaryRecord[] } + | { type: "instance.dataChanged"; instanceId: string; data: InstanceData } + | { type: "instance.event"; instanceId: string; event: InstanceStreamEvent } + | { type: "instance.eventStatus"; instanceId: string; status: InstanceStreamStatus; reason?: string } + | { type: "app.releaseAvailable"; release: LatestReleaseInfo } + +export interface NetworkAddress { + ip: string + family: "ipv4" | "ipv6" + scope: "external" | "internal" | "loopback" + url: string +} + +export interface LatestReleaseInfo { + version: string + tag: string + url: string + channel: "stable" | "dev" + publishedAt?: string + notes?: string +} + +export interface ServerMeta { + /** Base URL clients should target for REST calls (useful for Electron embedding). */ + httpBaseUrl: string + /** SSE endpoint advertised to clients (`/api/events` by default). */ + eventsUrl: string + /** Host the server is bound to (e.g., 127.0.0.1 or 0.0.0.0). */ + host: string + /** Listening mode derived from host binding. */ + listeningMode: "local" | "all" + /** Actual port in use after binding. */ + port: number + /** Display label for the host (e.g., hostname or friendly name). */ + hostLabel: string + /** Absolute path of the filesystem root exposed to clients. */ + workspaceRoot: string + /** Reachable addresses for this server, external first. */ + addresses: NetworkAddress[] + /** Optional metadata about the most recent public release. */ + latestRelease?: LatestReleaseInfo +} + +export interface PortAvailabilityResponse { + port: number +} + +export type { + Preferences, + ModelPreference, + AgentModelSelections, + RecentFolder, + OpenCodeBinary, +} diff --git a/packages/server/src/bin.ts b/packages/server/src/bin.ts new file mode 100644 index 0000000..9ec4fbc --- /dev/null +++ b/packages/server/src/bin.ts @@ -0,0 +1,29 @@ +#!/usr/bin/env node + +import { spawn } from "child_process" +import path from "path" +import { fileURLToPath, pathToFileURL } from "url" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const cliEntry = path.join(__dirname, "index.js") +const loaderFileUrl = pathToFileURL(path.join(__dirname, "loader.js")).href +const registerScript = `import { register } from "node:module"; import { pathToFileURL } from "node:url"; register("${encodeURI(loaderFileUrl)}", pathToFileURL("./"));` +const loaderArg = `data:text/javascript,${registerScript}` + +const child = spawn(process.execPath, ["--import", loaderArg, cliEntry, ...process.argv.slice(2)], { + stdio: "inherit", +}) + +child.on("exit", (code, signal) => { + if (signal) { + process.kill(process.pid, signal) + return + } + process.exit(code ?? 0) +}) + +child.on("error", (error) => { + console.error("Failed to launch CLI runtime", error) + process.exit(1) +}) diff --git a/packages/server/src/config/binaries.ts b/packages/server/src/config/binaries.ts new file mode 100644 index 0000000..7b3d4f5 --- /dev/null +++ b/packages/server/src/config/binaries.ts @@ -0,0 +1,156 @@ +import { + BinaryCreateRequest, + BinaryRecord, + BinaryUpdateRequest, + BinaryValidationResult, +} from "../api-types" +import { ConfigStore } from "./store" +import { EventBus } from "../events/bus" +import type { ConfigFile } from "./schema" +import { Logger } from "../logger" + +export class BinaryRegistry { + constructor( + private readonly configStore: ConfigStore, + private readonly eventBus: EventBus | undefined, + private readonly logger: Logger, + ) {} + + list(): BinaryRecord[] { + return this.mapRecords() + } + + resolveDefault(): BinaryRecord { + const binaries = this.mapRecords() + if (binaries.length === 0) { + this.logger.warn("No configured binaries found, falling back to opencode") + return this.buildFallbackRecord("opencode") + } + return binaries.find((binary) => binary.isDefault) ?? binaries[0] + } + + create(request: BinaryCreateRequest): BinaryRecord { + this.logger.debug({ path: request.path }, "Registering OpenCode binary") + const entry = { + path: request.path, + version: undefined, + lastUsed: Date.now(), + label: request.label, + } + + const config = this.configStore.get() + const nextConfig = this.cloneConfig(config) + const deduped = nextConfig.opencodeBinaries.filter((binary) => binary.path !== request.path) + nextConfig.opencodeBinaries = [entry, ...deduped] + + if (request.makeDefault) { + nextConfig.preferences.lastUsedBinary = request.path + } + + this.configStore.replace(nextConfig) + const record = this.getById(request.path) + this.emitChange() + return record + } + + update(id: string, updates: BinaryUpdateRequest): BinaryRecord { + this.logger.debug({ id }, "Updating OpenCode binary") + const config = this.configStore.get() + const nextConfig = this.cloneConfig(config) + nextConfig.opencodeBinaries = nextConfig.opencodeBinaries.map((binary) => + binary.path === id ? { ...binary, label: updates.label ?? binary.label } : binary, + ) + + if (updates.makeDefault) { + nextConfig.preferences.lastUsedBinary = id + } + + this.configStore.replace(nextConfig) + const record = this.getById(id) + this.emitChange() + return record + } + + remove(id: string) { + this.logger.debug({ id }, "Removing OpenCode binary") + const config = this.configStore.get() + const nextConfig = this.cloneConfig(config) + const remaining = nextConfig.opencodeBinaries.filter((binary) => binary.path !== id) + nextConfig.opencodeBinaries = remaining + + if (nextConfig.preferences.lastUsedBinary === id) { + nextConfig.preferences.lastUsedBinary = remaining[0]?.path + } + + this.configStore.replace(nextConfig) + this.emitChange() + } + + validatePath(path: string): BinaryValidationResult { + this.logger.debug({ path }, "Validating OpenCode binary path") + return this.validateRecord({ + id: path, + path, + label: this.prettyLabel(path), + isDefault: false, + }) + } + + private cloneConfig(config: ConfigFile): ConfigFile { + return JSON.parse(JSON.stringify(config)) as ConfigFile + } + + private mapRecords(): BinaryRecord[] { + + const config = this.configStore.get() + const configuredBinaries = config.opencodeBinaries.map((binary) => ({ + id: binary.path, + path: binary.path, + label: binary.label ?? this.prettyLabel(binary.path), + version: binary.version, + isDefault: false, + })) + + const defaultPath = config.preferences.lastUsedBinary ?? configuredBinaries[0]?.path ?? "opencode" + + const annotated = configuredBinaries.map((binary) => ({ + ...binary, + isDefault: binary.path === defaultPath, + })) + + if (!annotated.some((binary) => binary.path === defaultPath)) { + annotated.unshift(this.buildFallbackRecord(defaultPath)) + } + + return annotated + } + + private getById(id: string): BinaryRecord { + return this.mapRecords().find((binary) => binary.id === id) ?? this.buildFallbackRecord(id) + } + + private emitChange() { + this.logger.debug("Emitting binaries changed event") + this.eventBus?.publish({ type: "config.binariesChanged", binaries: this.mapRecords() }) + } + + private validateRecord(record: BinaryRecord): BinaryValidationResult { + // TODO: call actual binary -v check. + return { valid: true, version: record.version } + } + + private buildFallbackRecord(path: string): BinaryRecord { + return { + id: path, + path, + label: this.prettyLabel(path), + isDefault: true, + } + } + + private prettyLabel(path: string) { + const parts = path.split(/[\\/]/) + const last = parts[parts.length - 1] || path + return last || path + } +} diff --git a/packages/server/src/config/schema.ts b/packages/server/src/config/schema.ts new file mode 100644 index 0000000..09dadf6 --- /dev/null +++ b/packages/server/src/config/schema.ts @@ -0,0 +1,64 @@ +import { z } from "zod" + +const ModelPreferenceSchema = z.object({ + providerId: z.string(), + modelId: z.string(), +}) + +const AgentModelSelectionSchema = z.record(z.string(), ModelPreferenceSchema) +const AgentModelSelectionsSchema = z.record(z.string(), AgentModelSelectionSchema) + +const PreferencesSchema = z.object({ + showThinkingBlocks: z.boolean().default(false), + thinkingBlocksExpansion: z.enum(["expanded", "collapsed"]).default("expanded"), + showTimelineTools: z.boolean().default(true), + lastUsedBinary: z.string().optional(), + environmentVariables: z.record(z.string()).default({}), + modelRecents: z.array(ModelPreferenceSchema).default([]), + diffViewMode: z.enum(["split", "unified"]).default("split"), + toolOutputExpansion: z.enum(["expanded", "collapsed"]).default("expanded"), + diagnosticsExpansion: z.enum(["expanded", "collapsed"]).default("expanded"), + showUsageMetrics: z.boolean().default(true), + autoCleanupBlankSessions: z.boolean().default(true), + listeningMode: z.enum(["local", "all"]).default("local"), +}) + +const RecentFolderSchema = z.object({ + path: z.string(), + lastAccessed: z.number().nonnegative(), +}) + +const OpenCodeBinarySchema = z.object({ + path: z.string(), + version: z.string().optional(), + lastUsed: z.number().nonnegative(), + label: z.string().optional(), +}) + +const ConfigFileSchema = z.object({ + preferences: PreferencesSchema.default({}), + recentFolders: z.array(RecentFolderSchema).default([]), + opencodeBinaries: z.array(OpenCodeBinarySchema).default([]), + theme: z.enum(["light", "dark", "system"]).optional(), +}) + +const DEFAULT_CONFIG = ConfigFileSchema.parse({}) + +export { + ModelPreferenceSchema, + AgentModelSelectionSchema, + AgentModelSelectionsSchema, + PreferencesSchema, + RecentFolderSchema, + OpenCodeBinarySchema, + ConfigFileSchema, + DEFAULT_CONFIG, +} + +export type ModelPreference = z.infer +export type AgentModelSelection = z.infer +export type AgentModelSelections = z.infer +export type Preferences = z.infer +export type RecentFolder = z.infer +export type OpenCodeBinary = z.infer +export type ConfigFile = z.infer diff --git a/packages/server/src/config/store.ts b/packages/server/src/config/store.ts new file mode 100644 index 0000000..dda49e4 --- /dev/null +++ b/packages/server/src/config/store.ts @@ -0,0 +1,78 @@ +import fs from "fs" +import path from "path" +import { EventBus } from "../events/bus" +import { Logger } from "../logger" +import { ConfigFile, ConfigFileSchema, DEFAULT_CONFIG } from "./schema" + +export class ConfigStore { + private cache: ConfigFile = DEFAULT_CONFIG + private loaded = false + + constructor( + private readonly configPath: string, + private readonly eventBus: EventBus | undefined, + private readonly logger: Logger, + ) {} + + load(): ConfigFile { + if (this.loaded) { + return this.cache + } + + try { + const resolved = this.resolvePath(this.configPath) + if (fs.existsSync(resolved)) { + const content = fs.readFileSync(resolved, "utf-8") + const parsed = JSON.parse(content) + this.cache = ConfigFileSchema.parse(parsed) + this.logger.debug({ resolved }, "Loaded existing config file") + } else { + this.cache = DEFAULT_CONFIG + this.logger.debug({ resolved }, "No config file found, using defaults") + } + } catch (error) { + this.logger.warn({ err: error }, "Failed to load config, using defaults") + this.cache = DEFAULT_CONFIG + } + + this.loaded = true + return this.cache + } + + get(): ConfigFile { + return this.load() + } + + replace(config: ConfigFile) { + const validated = ConfigFileSchema.parse(config) + this.commit(validated) + } + + private commit(next: ConfigFile) { + this.cache = next + this.loaded = true + this.persist() + const published = Boolean(this.eventBus) + this.eventBus?.publish({ type: "config.appChanged", config: this.cache }) + this.logger.debug({ broadcast: published }, "Config SSE event emitted") + this.logger.trace({ config: this.cache }, "Config payload") + } + + private persist() { + try { + const resolved = this.resolvePath(this.configPath) + fs.mkdirSync(path.dirname(resolved), { recursive: true }) + fs.writeFileSync(resolved, JSON.stringify(this.cache, null, 2), "utf-8") + this.logger.debug({ resolved }, "Persisted config file") + } catch (error) { + this.logger.warn({ err: error }, "Failed to persist config") + } + } + + private resolvePath(filePath: string) { + if (filePath.startsWith("~/")) { + return path.join(process.env.HOME ?? "", filePath.slice(2)) + } + return path.resolve(filePath) + } +} diff --git a/packages/server/src/context-engine/client.ts b/packages/server/src/context-engine/client.ts new file mode 100644 index 0000000..a0a613a --- /dev/null +++ b/packages/server/src/context-engine/client.ts @@ -0,0 +1,189 @@ +/** + * Context Engine HTTP Client + * Communicates with the Context-Engine RAG service for code retrieval and memory management. + */ + +import { Logger } from "../logger" + +export interface ContextEngineConfig { + /** Base URL of the Context-Engine API (default: http://localhost:8000) */ + baseUrl: string + /** Request timeout in milliseconds (default: 30000) */ + timeout: number +} + +export interface IndexRequest { + path: string + recursive?: boolean +} + +export interface IndexResponse { + status: "started" | "completed" | "error" + indexed_files?: number + message?: string +} + +export interface QueryRequest { + query: string + context_window?: number + top_k?: number +} + +export interface QueryResponse { + results: Array<{ + content: string + file_path: string + score: number + metadata?: Record + }> + total_results: number +} + +export interface MemoryRequest { + text: string + metadata?: Record +} + +export interface MemoryResponse { + id: string + status: "added" | "error" +} + +export interface HealthResponse { + status: "healthy" | "unhealthy" + version?: string + indexed_files?: number +} + +const DEFAULT_CONFIG: ContextEngineConfig = { + baseUrl: "http://localhost:8000", + timeout: 30000, +} + +export class ContextEngineClient { + private config: ContextEngineConfig + private logger: Logger + + constructor(config: Partial = {}, logger: Logger) { + this.config = { ...DEFAULT_CONFIG, ...config } + this.logger = logger + } + + /** + * Check if the Context-Engine is healthy and responding + */ + async health(): Promise { + try { + const response = await this.request("/health", { + method: "GET", + }) + return response + } catch (error) { + this.logger.debug({ error }, "Context-Engine health check failed") + return { status: "unhealthy" } + } + } + + /** + * Trigger indexing for a project path + */ + async index(path: string, recursive = true): Promise { + this.logger.info({ path, recursive }, "Triggering Context-Engine indexing") + + try { + const response = await this.request("/index", { + method: "POST", + body: JSON.stringify({ path, recursive } as IndexRequest), + }) + this.logger.info({ path, response }, "Context-Engine indexing response") + return response + } catch (error) { + this.logger.error({ path, error }, "Context-Engine indexing failed") + return { + status: "error", + message: error instanceof Error ? error.message : "Unknown error", + } + } + } + + /** + * Query the Context-Engine for relevant code snippets + */ + async query(prompt: string, contextWindow = 4096, topK = 5): Promise { + this.logger.debug({ prompt: prompt.slice(0, 100), contextWindow, topK }, "Querying Context-Engine") + + try { + const response = await this.request("/query", { + method: "POST", + body: JSON.stringify({ + query: prompt, + context_window: contextWindow, + top_k: topK, + } as QueryRequest), + }) + this.logger.debug({ resultCount: response.results.length }, "Context-Engine query completed") + return response + } catch (error) { + this.logger.warn({ error }, "Context-Engine query failed") + return { results: [], total_results: 0 } + } + } + + /** + * Add a memory/rule to the Context-Engine for session-specific context + */ + async addMemory(text: string, metadata?: Record): Promise { + this.logger.debug({ textLength: text.length }, "Adding memory to Context-Engine") + + try { + const response = await this.request("/memory", { + method: "POST", + body: JSON.stringify({ text, metadata } as MemoryRequest), + }) + return response + } catch (error) { + this.logger.warn({ error }, "Context-Engine addMemory failed") + return { id: "", status: "error" } + } + } + + /** + * Get the current indexing status + */ + async getStatus(): Promise<{ indexing: boolean; indexed_files: number; last_indexed?: string }> { + try { + const response = await this.request<{ indexing: boolean; indexed_files: number; last_indexed?: string }>("/status", { + method: "GET", + }) + return response + } catch (error) { + return { indexing: false, indexed_files: 0 } + } + } + + private async request(endpoint: string, options: RequestInit): Promise { + const url = `${this.config.baseUrl}${endpoint}` + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), this.config.timeout) + + try { + const response = await fetch(url, { + ...options, + headers: { + "Content-Type": "application/json", + ...options.headers, + }, + signal: controller.signal, + }) + + if (!response.ok) { + const errorText = await response.text().catch(() => "") + throw new Error(`Context-Engine request failed: ${response.status} ${response.statusText} - ${errorText}`) + } + + return await response.json() as T + } finally { + clearTimeout(timeoutId) + } + } +} diff --git a/packages/server/src/context-engine/index.ts b/packages/server/src/context-engine/index.ts new file mode 100644 index 0000000..78b6111 --- /dev/null +++ b/packages/server/src/context-engine/index.ts @@ -0,0 +1,13 @@ +/** + * Context Engine module exports + */ + +export { ContextEngineClient, type ContextEngineConfig, type QueryResponse, type IndexResponse } from "./client" +export { + ContextEngineService, + type ContextEngineServiceConfig, + type ContextEngineStatus, + getContextEngineService, + initializeContextEngineService, + shutdownContextEngineService, +} from "./service" diff --git a/packages/server/src/context-engine/service.ts b/packages/server/src/context-engine/service.ts new file mode 100644 index 0000000..c015aca --- /dev/null +++ b/packages/server/src/context-engine/service.ts @@ -0,0 +1,350 @@ +/** + * Context Engine Service + * Manages the lifecycle of the Context-Engine process (Python sidecar) + * and provides access to the Context-Engine client. + */ + +import { spawn, ChildProcess } from "child_process" +import { EventEmitter } from "events" +import { Logger } from "../logger" +import { ContextEngineClient, ContextEngineConfig, HealthResponse } from "./client" + +export type ContextEngineStatus = "stopped" | "starting" | "ready" | "indexing" | "error" + +export interface ContextEngineServiceConfig { + /** Path to the context-engine executable or Python script */ + binaryPath?: string + /** Arguments to pass to the context-engine process */ + args?: string[] + /** Port for the Context-Engine API (default: 8000) */ + port: number + /** Host for the Context-Engine API (default: localhost) */ + host: string + /** Whether to auto-start the engine when first needed (lazy start) */ + lazyStart: boolean + /** Health check interval in milliseconds */ + healthCheckInterval: number + /** Max retries for health check before marking as error */ + maxHealthCheckRetries: number +} + +const DEFAULT_SERVICE_CONFIG: ContextEngineServiceConfig = { + binaryPath: "context-engine", + args: [], + port: 8000, + host: "localhost", + lazyStart: true, + healthCheckInterval: 5000, + maxHealthCheckRetries: 3, +} + +export class ContextEngineService extends EventEmitter { + private config: ContextEngineServiceConfig + private logger: Logger + private process: ChildProcess | null = null + private client: ContextEngineClient + private status: ContextEngineStatus = "stopped" + private healthCheckTimer: NodeJS.Timeout | null = null + private healthCheckFailures = 0 + private indexingPaths = new Set() + + constructor(config: Partial = {}, logger: Logger) { + super() + this.config = { ...DEFAULT_SERVICE_CONFIG, ...config } + this.logger = logger + + const clientConfig: Partial = { + baseUrl: `http://${this.config.host}:${this.config.port}`, + timeout: 30000, + } + this.client = new ContextEngineClient(clientConfig, logger) + } + + /** + * Get the current status of the Context-Engine + */ + getStatus(): ContextEngineStatus { + return this.status + } + + /** + * Check if the Context-Engine is ready to accept requests + */ + isReady(): boolean { + return this.status === "ready" || this.status === "indexing" + } + + /** + * Get the Context-Engine client for making API calls + */ + getClient(): ContextEngineClient { + return this.client + } + + /** + * Start the Context-Engine process + */ + async start(): Promise { + if (this.status === "ready" || this.status === "starting") { + this.logger.debug("Context-Engine already started or starting") + return true + } + + this.setStatus("starting") + this.logger.info({ config: this.config }, "Starting Context-Engine service") + + // First, check if an external Context-Engine is already running + const externalHealth = await this.client.health() + if (externalHealth.status === "healthy") { + this.logger.info("External Context-Engine detected and healthy") + this.setStatus("ready") + this.startHealthCheck() + return true + } + + // Try to spawn the process + if (!this.config.binaryPath) { + this.logger.warn("No binary path configured for Context-Engine") + this.setStatus("error") + return false + } + + try { + const args = [ + ...(this.config.args || []), + "--port", String(this.config.port), + "--host", this.config.host, + ] + + this.logger.info({ binary: this.config.binaryPath, args }, "Spawning Context-Engine process") + + this.process = spawn(this.config.binaryPath, args, { + stdio: ["ignore", "pipe", "pipe"], + shell: process.platform === "win32", + detached: false, + }) + + this.process.stdout?.on("data", (data) => { + this.logger.debug({ output: data.toString().trim() }, "Context-Engine stdout") + }) + + this.process.stderr?.on("data", (data) => { + this.logger.debug({ output: data.toString().trim() }, "Context-Engine stderr") + }) + + this.process.on("error", (error) => { + this.logger.error({ error }, "Context-Engine process error") + this.setStatus("error") + }) + + this.process.on("exit", (code, signal) => { + this.logger.info({ code, signal }, "Context-Engine process exited") + this.process = null + if (this.status !== "stopped") { + this.setStatus("error") + } + }) + + // Wait for the process to become ready + const ready = await this.waitForReady(30000) + if (ready) { + this.setStatus("ready") + this.startHealthCheck() + return true + } else { + this.logger.error("Context-Engine failed to become ready") + this.setStatus("error") + return false + } + } catch (error) { + this.logger.error({ error }, "Failed to spawn Context-Engine process") + this.setStatus("error") + return false + } + } + + /** + * Stop the Context-Engine process + */ + async stop(): Promise { + this.stopHealthCheck() + this.setStatus("stopped") + + if (this.process) { + this.logger.info("Stopping Context-Engine process") + this.process.kill("SIGTERM") + + // Wait for graceful shutdown + await new Promise((resolve) => { + const timeout = setTimeout(() => { + if (this.process) { + this.logger.warn("Context-Engine did not exit gracefully, killing") + this.process.kill("SIGKILL") + } + resolve() + }, 5000) + + if (this.process) { + this.process.once("exit", () => { + clearTimeout(timeout) + resolve() + }) + } else { + clearTimeout(timeout) + resolve() + } + }) + + this.process = null + } + } + + /** + * Trigger indexing for a workspace path (non-blocking) + */ + async indexPath(path: string): Promise { + if (!this.config.lazyStart && !this.isReady()) { + this.logger.debug({ path }, "Context-Engine not ready, skipping indexing") + return + } + + // Lazy start if needed + if (this.config.lazyStart && this.status === "stopped") { + this.logger.info({ path }, "Lazy-starting Context-Engine for indexing") + const started = await this.start() + if (!started) { + this.logger.warn({ path }, "Failed to start Context-Engine for indexing") + return + } + } + + if (this.indexingPaths.has(path)) { + this.logger.debug({ path }, "Path already being indexed") + return + } + + this.indexingPaths.add(path) + this.setStatus("indexing") + + // Fire and forget - don't block workspace creation + this.client.index(path).then((response) => { + this.indexingPaths.delete(path) + if (response.status === "error") { + this.logger.warn({ path, response }, "Context-Engine indexing failed") + } else { + this.logger.info({ path, indexed_files: response.indexed_files }, "Context-Engine indexing completed") + } + if (this.indexingPaths.size === 0 && this.status === "indexing") { + this.setStatus("ready") + } + this.emit("indexComplete", { path, response }) + }).catch((error) => { + this.indexingPaths.delete(path) + this.logger.error({ path, error }, "Context-Engine indexing error") + if (this.indexingPaths.size === 0 && this.status === "indexing") { + this.setStatus("ready") + } + }) + } + + /** + * Query the Context-Engine for relevant code snippets + */ + async query(prompt: string, contextWindow?: number): Promise { + if (!this.isReady()) { + return null + } + + try { + const response = await this.client.query(prompt, contextWindow) + if (response.results.length === 0) { + return null + } + + // Format the results as a context block + const contextParts = response.results.map((result, index) => { + return `// File: ${result.file_path} (relevance: ${(result.score * 100).toFixed(1)}%)\n${result.content}` + }) + + return `\n${contextParts.join("\n\n")}\n` + } catch (error) { + this.logger.warn({ error }, "Context-Engine query failed") + return null + } + } + + private setStatus(status: ContextEngineStatus): void { + if (this.status !== status) { + this.logger.info({ oldStatus: this.status, newStatus: status }, "Context-Engine status changed") + this.status = status + this.emit("statusChange", status) + } + } + + private async waitForReady(timeoutMs: number): Promise { + const startTime = Date.now() + const checkInterval = 500 + + while (Date.now() - startTime < timeoutMs) { + const health = await this.client.health() + if (health.status === "healthy") { + return true + } + await new Promise((resolve) => setTimeout(resolve, checkInterval)) + } + + return false + } + + private startHealthCheck(): void { + if (this.healthCheckTimer) return + + this.healthCheckTimer = setInterval(async () => { + const health = await this.client.health() + if (health.status === "healthy") { + this.healthCheckFailures = 0 + if (this.status === "error") { + this.setStatus("ready") + } + } else { + this.healthCheckFailures++ + if (this.healthCheckFailures >= this.config.maxHealthCheckRetries) { + this.logger.warn("Context-Engine health check failed multiple times") + this.setStatus("error") + } + } + }, this.config.healthCheckInterval) + } + + private stopHealthCheck(): void { + if (this.healthCheckTimer) { + clearInterval(this.healthCheckTimer) + this.healthCheckTimer = null + } + } +} + +// Singleton instance for global access +let globalContextEngineService: ContextEngineService | null = null + +export function getContextEngineService(): ContextEngineService | null { + return globalContextEngineService +} + +export function initializeContextEngineService( + config: Partial, + logger: Logger +): ContextEngineService { + if (globalContextEngineService) { + return globalContextEngineService + } + globalContextEngineService = new ContextEngineService(config, logger) + return globalContextEngineService +} + +export async function shutdownContextEngineService(): Promise { + if (globalContextEngineService) { + await globalContextEngineService.stop() + globalContextEngineService = null + } +} diff --git a/packages/server/src/events/bus.ts b/packages/server/src/events/bus.ts new file mode 100644 index 0000000..3d417ce --- /dev/null +++ b/packages/server/src/events/bus.ts @@ -0,0 +1,47 @@ +import { EventEmitter } from "events" +import { WorkspaceEventPayload } from "../api-types" +import { Logger } from "../logger" + +export class EventBus extends EventEmitter { + constructor(private readonly logger?: Logger) { + super() + } + + publish(event: WorkspaceEventPayload): boolean { + if (event.type !== "instance.event" && event.type !== "instance.eventStatus") { + this.logger?.debug({ type: event.type }, "Publishing workspace event") + if (this.logger?.isLevelEnabled("trace")) { + this.logger.trace({ event }, "Workspace event payload") + } + } + return super.emit(event.type, event) + } + + onEvent(listener: (event: WorkspaceEventPayload) => void) { + const handler = (event: WorkspaceEventPayload) => listener(event) + this.on("workspace.created", handler) + this.on("workspace.started", handler) + this.on("workspace.error", handler) + this.on("workspace.stopped", handler) + this.on("workspace.log", handler) + this.on("config.appChanged", handler) + this.on("config.binariesChanged", handler) + this.on("instance.dataChanged", handler) + this.on("instance.event", handler) + this.on("instance.eventStatus", handler) + this.on("app.releaseAvailable", handler) + return () => { + this.off("workspace.created", handler) + this.off("workspace.started", handler) + this.off("workspace.error", handler) + this.off("workspace.stopped", handler) + this.off("workspace.log", handler) + this.off("config.appChanged", handler) + this.off("config.binariesChanged", handler) + this.off("instance.dataChanged", handler) + this.off("instance.event", handler) + this.off("instance.eventStatus", handler) + this.off("app.releaseAvailable", handler) + } + } +} diff --git a/packages/server/src/filesystem/__tests__/search-cache.test.ts b/packages/server/src/filesystem/__tests__/search-cache.test.ts new file mode 100644 index 0000000..f1facf9 --- /dev/null +++ b/packages/server/src/filesystem/__tests__/search-cache.test.ts @@ -0,0 +1,61 @@ +import assert from "node:assert/strict" +import { beforeEach, describe, it } from "node:test" +import type { FileSystemEntry } from "../../api-types" +import { + clearWorkspaceSearchCache, + getWorkspaceCandidates, + refreshWorkspaceCandidates, + WORKSPACE_CANDIDATE_CACHE_TTL_MS, +} from "../search-cache" + +describe("workspace search cache", () => { + beforeEach(() => { + clearWorkspaceSearchCache() + }) + + it("expires cached candidates after the TTL", () => { + const workspacePath = "/tmp/workspace" + const startTime = 1_000 + + refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-a")], startTime) + + const beforeExpiry = getWorkspaceCandidates( + workspacePath, + startTime + WORKSPACE_CANDIDATE_CACHE_TTL_MS - 1, + ) + assert.ok(beforeExpiry) + assert.equal(beforeExpiry.length, 1) + assert.equal(beforeExpiry[0].name, "file-a") + + const afterExpiry = getWorkspaceCandidates( + workspacePath, + startTime + WORKSPACE_CANDIDATE_CACHE_TTL_MS + 1, + ) + assert.equal(afterExpiry, undefined) + }) + + it("replaces cached entries when manually refreshed", () => { + const workspacePath = "/tmp/workspace" + + refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-a")], 5_000) + const initial = getWorkspaceCandidates(workspacePath) + assert.ok(initial) + assert.equal(initial[0].name, "file-a") + + refreshWorkspaceCandidates(workspacePath, () => [createEntry("file-b")], 6_000) + const refreshed = getWorkspaceCandidates(workspacePath) + assert.ok(refreshed) + assert.equal(refreshed[0].name, "file-b") + }) +}) + +function createEntry(name: string): FileSystemEntry { + return { + name, + path: name, + absolutePath: `/tmp/${name}`, + type: "file", + size: 1, + modifiedAt: new Date().toISOString(), + } +} diff --git a/packages/server/src/filesystem/browser.ts b/packages/server/src/filesystem/browser.ts new file mode 100644 index 0000000..29ddb1c --- /dev/null +++ b/packages/server/src/filesystem/browser.ts @@ -0,0 +1,295 @@ +import fs from "fs" +import os from "os" +import path from "path" +import { + FileSystemEntry, + FileSystemListResponse, + FileSystemListingMetadata, + WINDOWS_DRIVES_ROOT, +} from "../api-types" + +interface FileSystemBrowserOptions { + rootDir: string + unrestricted?: boolean +} + +interface DirectoryReadOptions { + includeFiles: boolean + formatPath: (entryName: string) => string + formatAbsolutePath: (entryName: string) => string +} + +const WINDOWS_DRIVE_LETTERS = Array.from({ length: 26 }, (_, i) => String.fromCharCode(65 + i)) + +export class FileSystemBrowser { + private readonly root: string + private readonly unrestricted: boolean + private readonly homeDir: string + private readonly isWindows: boolean + + constructor(options: FileSystemBrowserOptions) { + this.root = path.resolve(options.rootDir) + this.unrestricted = Boolean(options.unrestricted) + this.homeDir = os.homedir() + this.isWindows = process.platform === "win32" + } + + list(relativePath = ".", options: { includeFiles?: boolean } = {}): FileSystemEntry[] { + if (this.unrestricted) { + throw new Error("Relative listing is unavailable when running with unrestricted root") + } + const includeFiles = options.includeFiles ?? true + const normalizedPath = this.normalizeRelativePath(relativePath) + const absolutePath = this.toRestrictedAbsolute(normalizedPath) + return this.readDirectoryEntries(absolutePath, { + includeFiles, + formatPath: (entryName) => this.buildRelativePath(normalizedPath, entryName), + formatAbsolutePath: (entryName) => this.resolveRestrictedAbsoluteChild(normalizedPath, entryName), + }) + } + + browse(targetPath?: string, options: { includeFiles?: boolean } = {}): FileSystemListResponse { + const includeFiles = options.includeFiles ?? true + if (this.unrestricted) { + return this.listUnrestricted(targetPath, includeFiles) + } + return this.listRestrictedWithMetadata(targetPath, includeFiles) + } + + readFile(relativePath: string): string { + if (this.unrestricted) { + throw new Error("readFile is not available in unrestricted mode") + } + const resolved = this.toRestrictedAbsolute(relativePath) + return fs.readFileSync(resolved, "utf-8") + } + + private listRestrictedWithMetadata(relativePath: string | undefined, includeFiles: boolean): FileSystemListResponse { + const normalizedPath = this.normalizeRelativePath(relativePath) + const absolutePath = this.toRestrictedAbsolute(normalizedPath) + const entries = this.readDirectoryEntries(absolutePath, { + includeFiles, + formatPath: (entryName) => this.buildRelativePath(normalizedPath, entryName), + formatAbsolutePath: (entryName) => this.resolveRestrictedAbsoluteChild(normalizedPath, entryName), + }) + + const metadata: FileSystemListingMetadata = { + scope: "restricted", + currentPath: normalizedPath, + parentPath: normalizedPath === "." ? undefined : this.getRestrictedParent(normalizedPath), + rootPath: this.root, + homePath: this.homeDir, + displayPath: this.resolveRestrictedAbsolute(normalizedPath), + pathKind: "relative", + } + + return { entries, metadata } + } + + private listUnrestricted(targetPath: string | undefined, includeFiles: boolean): FileSystemListResponse { + const resolvedPath = this.resolveUnrestrictedPath(targetPath) + + if (this.isWindows && resolvedPath === WINDOWS_DRIVES_ROOT) { + return this.listWindowsDrives() + } + + const entries = this.readDirectoryEntries(resolvedPath, { + includeFiles, + formatPath: (entryName) => this.resolveAbsoluteChild(resolvedPath, entryName), + formatAbsolutePath: (entryName) => this.resolveAbsoluteChild(resolvedPath, entryName), + }) + + const parentPath = this.getUnrestrictedParent(resolvedPath) + + const metadata: FileSystemListingMetadata = { + scope: "unrestricted", + currentPath: resolvedPath, + parentPath, + rootPath: this.homeDir, + homePath: this.homeDir, + displayPath: resolvedPath, + pathKind: "absolute", + } + + return { entries, metadata } + } + + private listWindowsDrives(): FileSystemListResponse { + if (!this.isWindows) { + throw new Error("Drive listing is only supported on Windows hosts") + } + + const entries: FileSystemEntry[] = [] + for (const letter of WINDOWS_DRIVE_LETTERS) { + const drivePath = `${letter}:\\` + try { + if (fs.existsSync(drivePath)) { + entries.push({ + name: `${letter}:`, + path: drivePath, + absolutePath: drivePath, + type: "directory", + }) + } + } catch { + // Ignore inaccessible drives + } + } + + // Provide a generic UNC root entry so users can navigate to network shares manually. + entries.push({ + name: "UNC Network", + path: "\\\\", + absolutePath: "\\\\", + type: "directory", + }) + + const metadata: FileSystemListingMetadata = { + scope: "unrestricted", + currentPath: WINDOWS_DRIVES_ROOT, + parentPath: undefined, + rootPath: this.homeDir, + homePath: this.homeDir, + displayPath: "Drives", + pathKind: "drives", + } + + return { entries, metadata } + } + + private readDirectoryEntries(directory: string, options: DirectoryReadOptions): FileSystemEntry[] { + const dirents = fs.readdirSync(directory, { withFileTypes: true }) + const results: FileSystemEntry[] = [] + + for (const entry of dirents) { + if (!options.includeFiles && !entry.isDirectory()) { + continue + } + + const absoluteEntryPath = path.join(directory, entry.name) + let stats: fs.Stats + try { + stats = fs.statSync(absoluteEntryPath) + } catch { + // Skip entries we cannot stat (insufficient permissions, etc.) + continue + } + + const isDirectory = entry.isDirectory() + if (!options.includeFiles && !isDirectory) { + continue + } + + results.push({ + name: entry.name, + path: options.formatPath(entry.name), + absolutePath: options.formatAbsolutePath(entry.name), + type: isDirectory ? "directory" : "file", + size: isDirectory ? undefined : stats.size, + modifiedAt: stats.mtime.toISOString(), + }) + } + + return results.sort((a, b) => a.name.localeCompare(b.name)) + } + + private normalizeRelativePath(input: string | undefined) { + if (!input || input === "." || input === "./" || input === "/") { + return "." + } + let normalized = input.replace(/\\+/g, "/") + if (normalized.startsWith("./")) { + normalized = normalized.replace(/^\.\/+/, "") + } + if (normalized.startsWith("/")) { + normalized = normalized.replace(/^\/+/g, "") + } + return normalized === "" ? "." : normalized + } + + private buildRelativePath(parent: string, child: string) { + if (!parent || parent === ".") { + return this.normalizeRelativePath(child) + } + return this.normalizeRelativePath(`${parent}/${child}`) + } + + private resolveRestrictedAbsolute(relativePath: string) { + return this.toRestrictedAbsolute(relativePath) + } + + private resolveRestrictedAbsoluteChild(parent: string, child: string) { + const normalized = this.buildRelativePath(parent, child) + return this.toRestrictedAbsolute(normalized) + } + + private toRestrictedAbsolute(relativePath: string) { + const normalized = this.normalizeRelativePath(relativePath) + const target = path.resolve(this.root, normalized) + const relativeToRoot = path.relative(this.root, target) + if (relativeToRoot.startsWith("..") || path.isAbsolute(relativeToRoot) && relativeToRoot !== "") { + throw new Error("Access outside of root is not allowed") + } + return target + } + + private resolveUnrestrictedPath(input: string | undefined): string { + if (!input || input === "." || input === "./") { + return this.homeDir + } + + if (this.isWindows) { + if (input === WINDOWS_DRIVES_ROOT) { + return WINDOWS_DRIVES_ROOT + } + const normalized = path.win32.normalize(input) + if (/^[a-zA-Z]:/.test(normalized) || normalized.startsWith("\\\\")) { + return normalized + } + return path.win32.resolve(this.homeDir, normalized) + } + + if (input.startsWith("/")) { + return path.posix.normalize(input) + } + + return path.posix.resolve(this.homeDir, input) + } + + private resolveAbsoluteChild(parent: string, child: string) { + if (this.isWindows) { + return path.win32.normalize(path.win32.join(parent, child)) + } + return path.posix.normalize(path.posix.join(parent, child)) + } + + private getRestrictedParent(relativePath: string) { + const normalized = this.normalizeRelativePath(relativePath) + if (normalized === ".") { + return undefined + } + const segments = normalized.split("/") + segments.pop() + return segments.length === 0 ? "." : segments.join("/") + } + + private getUnrestrictedParent(currentPath: string) { + if (this.isWindows) { + const normalized = path.win32.normalize(currentPath) + const parsed = path.win32.parse(normalized) + if (normalized === WINDOWS_DRIVES_ROOT) { + return undefined + } + if (normalized === parsed.root) { + return WINDOWS_DRIVES_ROOT + } + return path.win32.dirname(normalized) + } + + const normalized = path.posix.normalize(currentPath) + if (normalized === "/") { + return undefined + } + return path.posix.dirname(normalized) + } +} diff --git a/packages/server/src/filesystem/search-cache.ts b/packages/server/src/filesystem/search-cache.ts new file mode 100644 index 0000000..5568204 --- /dev/null +++ b/packages/server/src/filesystem/search-cache.ts @@ -0,0 +1,66 @@ +import path from "path" +import type { FileSystemEntry } from "../api-types" + +export const WORKSPACE_CANDIDATE_CACHE_TTL_MS = 30_000 + +interface WorkspaceCandidateCacheEntry { + expiresAt: number + candidates: FileSystemEntry[] +} + +const workspaceCandidateCache = new Map() + +export function getWorkspaceCandidates(rootDir: string, now = Date.now()): FileSystemEntry[] | undefined { + const key = normalizeKey(rootDir) + const cached = workspaceCandidateCache.get(key) + if (!cached) { + return undefined + } + + if (cached.expiresAt <= now) { + workspaceCandidateCache.delete(key) + return undefined + } + + return cloneEntries(cached.candidates) +} + +export function refreshWorkspaceCandidates( + rootDir: string, + builder: () => FileSystemEntry[], + now = Date.now(), +): FileSystemEntry[] { + const key = normalizeKey(rootDir) + const freshCandidates = builder() + + if (!freshCandidates || freshCandidates.length === 0) { + workspaceCandidateCache.delete(key) + return [] + } + + const storedCandidates = cloneEntries(freshCandidates) + workspaceCandidateCache.set(key, { + expiresAt: now + WORKSPACE_CANDIDATE_CACHE_TTL_MS, + candidates: storedCandidates, + }) + + return cloneEntries(storedCandidates) +} + +export function clearWorkspaceSearchCache(rootDir?: string) { + if (typeof rootDir === "undefined") { + workspaceCandidateCache.clear() + return + } + + const key = normalizeKey(rootDir) + workspaceCandidateCache.delete(key) +} + +function cloneEntries(entries: FileSystemEntry[]): FileSystemEntry[] { + return entries.map((entry) => ({ ...entry })) +} + +function normalizeKey(rootDir: string) { + return path.resolve(rootDir) +} diff --git a/packages/server/src/filesystem/search.ts b/packages/server/src/filesystem/search.ts new file mode 100644 index 0000000..77347b0 --- /dev/null +++ b/packages/server/src/filesystem/search.ts @@ -0,0 +1,184 @@ +import fs from "fs" +import path from "path" +import fuzzysort from "fuzzysort" +import type { FileSystemEntry } from "../api-types" +import { clearWorkspaceSearchCache, getWorkspaceCandidates, refreshWorkspaceCandidates } from "./search-cache" + +const DEFAULT_LIMIT = 100 +const MAX_LIMIT = 200 +const MAX_CANDIDATES = 8000 +const IGNORED_DIRECTORIES = new Set( + [".git", ".hg", ".svn", "node_modules", "dist", "build", ".next", ".nuxt", ".turbo", ".cache", "coverage"].map( + (name) => name.toLowerCase(), + ), +) + +export type WorkspaceFileSearchType = "all" | "file" | "directory" + +export interface WorkspaceFileSearchOptions { + limit?: number + type?: WorkspaceFileSearchType + refresh?: boolean +} + +interface CandidateEntry { + entry: FileSystemEntry + key: string +} + +export function searchWorkspaceFiles( + rootDir: string, + query: string, + options: WorkspaceFileSearchOptions = {}, +): FileSystemEntry[] { + const trimmedQuery = query.trim() + if (!trimmedQuery) { + throw new Error("Search query is required") + } + + const normalizedRoot = path.resolve(rootDir) + const limit = normalizeLimit(options.limit) + const typeFilter: WorkspaceFileSearchType = options.type ?? "all" + const refreshRequested = options.refresh === true + + let entries: FileSystemEntry[] | undefined + + try { + if (!refreshRequested) { + entries = getWorkspaceCandidates(normalizedRoot) + } + + if (!entries) { + entries = refreshWorkspaceCandidates(normalizedRoot, () => collectCandidates(normalizedRoot)) + } + } catch (error) { + clearWorkspaceSearchCache(normalizedRoot) + throw error + } + + if (!entries || entries.length === 0) { + clearWorkspaceSearchCache(normalizedRoot) + return [] + } + + const candidates = buildCandidateEntries(entries, typeFilter) + + if (candidates.length === 0) { + return [] + } + + const matches = fuzzysort.go(trimmedQuery, candidates, { + key: "key", + limit, + }) + + if (!matches || matches.length === 0) { + return [] + } + + return matches.map((match) => match.obj.entry) +} + + +function collectCandidates(rootDir: string): FileSystemEntry[] { + const queue: string[] = [""] + const entries: FileSystemEntry[] = [] + + while (queue.length > 0 && entries.length < MAX_CANDIDATES) { + const relativeDir = queue.pop() || "" + const absoluteDir = relativeDir ? path.join(rootDir, relativeDir) : rootDir + + let dirents: fs.Dirent[] + try { + dirents = fs.readdirSync(absoluteDir, { withFileTypes: true }) + } catch { + continue + } + + for (const dirent of dirents) { + const entryName = dirent.name + const lowerName = entryName.toLowerCase() + const relativePath = relativeDir ? `${relativeDir}/${entryName}` : entryName + const absolutePath = path.join(absoluteDir, entryName) + + if (dirent.isDirectory() && IGNORED_DIRECTORIES.has(lowerName)) { + continue + } + + let stats: fs.Stats + try { + stats = fs.statSync(absolutePath) + } catch { + continue + } + + const isDirectory = stats.isDirectory() + + if (isDirectory && !IGNORED_DIRECTORIES.has(lowerName)) { + if (entries.length < MAX_CANDIDATES) { + queue.push(relativePath) + } + } + + const entryType: FileSystemEntry["type"] = isDirectory ? "directory" : "file" + const normalizedPath = normalizeRelativeEntryPath(relativePath) + const entry: FileSystemEntry = { + name: entryName, + path: normalizedPath, + absolutePath: path.resolve(rootDir, normalizedPath === "." ? "" : normalizedPath), + type: entryType, + size: entryType === "file" ? stats.size : undefined, + modifiedAt: stats.mtime.toISOString(), + } + + entries.push(entry) + + if (entries.length >= MAX_CANDIDATES) { + break + } + } + } + + return entries +} + +function buildCandidateEntries(entries: FileSystemEntry[], filter: WorkspaceFileSearchType): CandidateEntry[] { + const filtered: CandidateEntry[] = [] + for (const entry of entries) { + if (!shouldInclude(entry.type, filter)) { + continue + } + filtered.push({ entry, key: buildSearchKey(entry) }) + } + return filtered +} + +function normalizeLimit(limit?: number) { + if (!limit || Number.isNaN(limit)) { + return DEFAULT_LIMIT + } + const clamped = Math.min(Math.max(limit, 1), MAX_LIMIT) + return clamped +} + +function shouldInclude(entryType: FileSystemEntry["type"], filter: WorkspaceFileSearchType) { + return filter === "all" || entryType === filter +} + +function normalizeRelativeEntryPath(relativePath: string): string { + if (!relativePath) { + return "." + } + let normalized = relativePath.replace(/\\+/g, "/") + if (normalized.startsWith("./")) { + normalized = normalized.replace(/^\.\/+/, "") + } + if (normalized.startsWith("/")) { + normalized = normalized.replace(/^\/+/g, "") + } + return normalized || "." +} + +function buildSearchKey(entry: FileSystemEntry) { + return entry.path.toLowerCase() +} diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts new file mode 100644 index 0000000..b22531a --- /dev/null +++ b/packages/server/src/index.ts @@ -0,0 +1,246 @@ +/** + * CLI entry point. + * For now this only wires the typed modules together; actual command handling comes later. + */ +import { Command, InvalidArgumentError, Option } from "commander" +import path from "path" +import { fileURLToPath } from "url" +import { createRequire } from "module" +import { createHttpServer } from "./server/http-server" +import { WorkspaceManager } from "./workspaces/manager" +import { ConfigStore } from "./config/store" +import { BinaryRegistry } from "./config/binaries" +import { FileSystemBrowser } from "./filesystem/browser" +import { EventBus } from "./events/bus" +import { ServerMeta } from "./api-types" +import { InstanceStore } from "./storage/instance-store" +import { InstanceEventBridge } from "./workspaces/instance-events" +import { createLogger } from "./logger" +import { getUserConfigPath } from "./user-data" +import { launchInBrowser } from "./launcher" +import { startReleaseMonitor } from "./releases/release-monitor" +import { initializeContextEngineService, shutdownContextEngineService } from "./context-engine" + +const require = createRequire(import.meta.url) + +const packageJson = require("../package.json") as { version: string } +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const DEFAULT_UI_STATIC_DIR = path.resolve(__dirname, "../public") + +interface CliOptions { + port: number + host: string + rootDir: string + configPath: string + unrestrictedRoot: boolean + logLevel?: string + logDestination?: string + uiStaticDir: string + uiDevServer?: string + launch: boolean +} + +const DEFAULT_PORT = 9898 +const DEFAULT_HOST = "127.0.0.1" +const DEFAULT_CONFIG_PATH = getUserConfigPath() + +function parseCliOptions(argv: string[]): CliOptions { + const program = new Command() + .name("codenomad") + .description("CodeNomad CLI server") + .version(packageJson.version, "-v, --version", "Show the CLI version") + .addOption(new Option("--host ", "Host interface to bind").env("CLI_HOST").default(DEFAULT_HOST)) + .addOption(new Option("--port ", "Port for the HTTP server").env("CLI_PORT").default(DEFAULT_PORT).argParser(parsePort)) + .addOption( + new Option("--workspace-root ", "Workspace root directory").env("CLI_WORKSPACE_ROOT").default(process.cwd()), + ) + .addOption(new Option("--root ").env("CLI_ROOT").hideHelp(true)) + .addOption(new Option("--unrestricted-root", "Allow browsing the full filesystem").env("CLI_UNRESTRICTED_ROOT").default(false)) + .addOption(new Option("--config ", "Path to the config file").env("CLI_CONFIG").default(DEFAULT_CONFIG_PATH)) + .addOption(new Option("--log-level ", "Log level (trace|debug|info|warn|error)").env("CLI_LOG_LEVEL")) + .addOption(new Option("--log-destination ", "Log destination file (defaults to stdout)").env("CLI_LOG_DESTINATION")) + .addOption( + new Option("--ui-dir ", "Directory containing the built UI bundle").env("CLI_UI_DIR").default(DEFAULT_UI_STATIC_DIR), + ) + .addOption(new Option("--ui-dev-server ", "Proxy UI requests to a running dev server").env("CLI_UI_DEV_SERVER")) + .addOption(new Option("--launch", "Launch the UI in a browser after start").env("CLI_LAUNCH").default(false)) + + program.parse(argv, { from: "user" }) + const parsed = program.opts<{ + host: string + port: number + workspaceRoot?: string + root?: string + unrestrictedRoot?: boolean + config: string + logLevel?: string + logDestination?: string + uiDir: string + uiDevServer?: string + launch?: boolean + }>() + + const resolvedRoot = parsed.workspaceRoot ?? parsed.root ?? process.cwd() + + const normalizedHost = resolveHost(parsed.host) + + return { + port: parsed.port, + host: normalizedHost, + rootDir: resolvedRoot, + configPath: parsed.config, + unrestrictedRoot: Boolean(parsed.unrestrictedRoot), + logLevel: parsed.logLevel, + logDestination: parsed.logDestination, + uiStaticDir: parsed.uiDir, + uiDevServer: parsed.uiDevServer, + launch: Boolean(parsed.launch), + } +} + +function parsePort(input: string): number { + const value = Number(input) + if (!Number.isInteger(value) || value < 0 || value > 65535) { + throw new InvalidArgumentError("Port must be an integer between 0 and 65535") + } + return value +} + +function resolveHost(input: string | undefined): string { + if (input && input.trim() === "0.0.0.0") { + return "0.0.0.0" + } + return DEFAULT_HOST +} + +async function main() { + const options = parseCliOptions(process.argv.slice(2)) + const logger = createLogger({ level: options.logLevel, destination: options.logDestination, component: "app" }) + const workspaceLogger = logger.child({ component: "workspace" }) + const configLogger = logger.child({ component: "config" }) + const eventLogger = logger.child({ component: "events" }) + + logger.info({ options }, "Starting CodeNomad CLI server") + + const eventBus = new EventBus(eventLogger) + const configStore = new ConfigStore(options.configPath, eventBus, configLogger) + const binaryRegistry = new BinaryRegistry(configStore, eventBus, configLogger) + const workspaceManager = new WorkspaceManager({ + rootDir: options.rootDir, + configStore, + binaryRegistry, + eventBus, + logger: workspaceLogger, + }) + const fileSystemBrowser = new FileSystemBrowser({ rootDir: options.rootDir, unrestricted: options.unrestrictedRoot }) + const instanceStore = new InstanceStore() + const instanceEventBridge = new InstanceEventBridge({ + workspaceManager, + eventBus, + logger: logger.child({ component: "instance-events" }), + }) + + // Initialize Context-Engine service (lazy start - starts when first workspace opens) + const contextEngineService = initializeContextEngineService( + { + lazyStart: true, + port: 8000, + host: "localhost", + }, + logger.child({ component: "context-engine" }) + ) + + const serverMeta: ServerMeta = { + httpBaseUrl: `http://${options.host}:${options.port}`, + eventsUrl: `/api/events`, + host: options.host, + listeningMode: options.host === "0.0.0.0" ? "all" : "local", + port: options.port, + hostLabel: options.host, + workspaceRoot: options.rootDir, + addresses: [], + } + + const releaseMonitor = startReleaseMonitor({ + currentVersion: packageJson.version, + logger: logger.child({ component: "release-monitor" }), + onUpdate: (release) => { + if (release) { + serverMeta.latestRelease = release + eventBus.publish({ type: "app.releaseAvailable", release }) + } else { + delete serverMeta.latestRelease + } + }, + }) + + const server = createHttpServer({ + host: options.host, + port: options.port, + workspaceManager, + configStore, + binaryRegistry, + fileSystemBrowser, + eventBus, + serverMeta, + instanceStore, + uiStaticDir: options.uiStaticDir, + uiDevServerUrl: options.uiDevServer, + logger, + }) + + const startInfo = await server.start() + logger.info({ port: startInfo.port, host: options.host }, "HTTP server listening") + console.log(`CodeNomad Server is ready at ${startInfo.url}`) + + if (options.launch) { + await launchInBrowser(startInfo.url, logger.child({ component: "launcher" })) + } + + let shuttingDown = false + + const shutdown = async () => { + if (shuttingDown) { + logger.info("Shutdown already in progress, ignoring signal") + return + } + shuttingDown = true + logger.info("Received shutdown signal, closing server") + try { + await server.stop() + logger.info("HTTP server stopped") + } catch (error) { + logger.error({ err: error }, "Failed to stop HTTP server") + } + + try { + instanceEventBridge.shutdown() + await workspaceManager.shutdown() + logger.info("Workspace manager shutdown complete") + } catch (error) { + logger.error({ err: error }, "Workspace manager shutdown failed") + } + + try { + await shutdownContextEngineService() + logger.info("Context-Engine shutdown complete") + } catch (error) { + logger.error({ err: error }, "Context-Engine shutdown failed") + } + + releaseMonitor.stop() + + logger.info("Exiting process") + process.exit(0) + } + + process.on("SIGINT", shutdown) + process.on("SIGTERM", shutdown) +} + +main().catch((error) => { + const logger = createLogger({ component: "app" }) + logger.error({ err: error }, "CLI server crashed") + process.exit(1) +}) diff --git a/packages/server/src/integrations/ollama-cloud.ts b/packages/server/src/integrations/ollama-cloud.ts new file mode 100644 index 0000000..7448c50 --- /dev/null +++ b/packages/server/src/integrations/ollama-cloud.ts @@ -0,0 +1,537 @@ +import { z } from "zod" +import { getContextEngineService } from "../context-engine" + +export const OllamaCloudConfigSchema = z.object({ + apiKey: z.string().optional(), + endpoint: z.string().default("https://ollama.com"), + enabled: z.boolean().default(false) +}) + +export type OllamaCloudConfig = z.infer + +// Schema is flexible since Ollama Cloud may return different fields than local Ollama +export const OllamaModelSchema = z.object({ + name: z.string(), + model: z.string().optional(), // Some APIs return model instead of name + size: z.union([z.string(), z.number()]).optional(), + digest: z.string().optional(), + modified_at: z.string().optional(), + created_at: z.string().optional(), + details: z.any().optional() // Model details like family, parameter_size, etc. +}) + +export type OllamaModel = z.infer + +export const ChatMessageSchema = z.object({ + role: z.enum(["user", "assistant", "system"]), + content: z.string(), + images: z.array(z.string()).optional(), + tool_calls: z.array(z.any()).optional(), + thinking: z.string().optional() +}) + +export type ChatMessage = z.infer + +export const ToolCallSchema = z.object({ + name: z.string(), + arguments: z.record(z.any()) +}) + +export type ToolCall = z.infer + +export const ToolDefinitionSchema = z.object({ + name: z.string(), + description: z.string(), + parameters: z.object({ + type: z.enum(["object", "string", "number", "boolean", "array"]), + properties: z.record(z.any()), + required: z.array(z.string()).optional() + }) +}) + +export type ToolDefinition = z.infer + +export const ChatRequestSchema = z.object({ + model: z.string(), + messages: z.array(ChatMessageSchema), + stream: z.boolean().default(false), + think: z.union([z.boolean(), z.enum(["low", "medium", "high"])]).optional(), + format: z.union([z.literal("json"), z.any()]).optional(), + tools: z.array(ToolDefinitionSchema).optional(), + web_search: z.boolean().optional(), + options: z.object({ + temperature: z.number().min(0).max(2).optional(), + top_p: z.number().min(0).max(1).optional() + }).optional() +}) + +export const ChatResponseSchema = z.object({ + model: z.string(), + created_at: z.string(), + message: ChatMessageSchema.extend({ + thinking: z.string().optional(), + tool_calls: z.array(z.any()).optional() + }), + done: z.boolean().optional(), + total_duration: z.number().optional(), + load_duration: z.number().optional(), + prompt_eval_count: z.number().optional(), + prompt_eval_duration: z.number().optional(), + eval_count: z.number().optional(), + eval_duration: z.number().optional() +}) + +export type ChatRequest = z.infer +export type ChatResponse = z.infer + +export const EmbeddingRequestSchema = z.object({ + model: z.string(), + input: z.union([z.string(), z.array(z.string())]) +}) + +export type EmbeddingRequest = z.infer + +export const EmbeddingResponseSchema = z.object({ + model: z.string(), + embeddings: z.array(z.array(z.number())) +}) + +export type EmbeddingResponse = z.infer + +export class OllamaCloudClient { + private config: OllamaCloudConfig + private baseUrl: string + + constructor(config: OllamaCloudConfig) { + this.config = config + this.baseUrl = config.endpoint.replace(/\/$/, "") + } + + async testConnection(): Promise { + try { + const response = await this.makeRequest("/tags", { method: "GET" }) + return response.ok + } catch (error) { + console.error("Ollama Cloud connection test failed:", error) + return false + } + } + + async listModels(): Promise { + try { + const headers: Record = {} + if (this.config.apiKey) { + headers["Authorization"] = `Bearer ${this.config.apiKey}` + } + + const cloudResponse = await fetch(`${this.baseUrl}/v1/models`, { + method: "GET", + headers + }) + + if (cloudResponse.ok) { + const data = await cloudResponse.json() + const modelsArray = Array.isArray(data?.data) ? data.data : [] + const parsedModels = modelsArray + .map((model: any) => ({ + name: model.id || model.name || model.model, + model: model.id || model.model || model.name, + })) + .filter((model: any) => model.name) + + if (parsedModels.length > 0) { + return parsedModels + } + } + + const response = await this.makeRequest("/tags", { method: "GET" }) + + if (!response.ok) { + const errorText = await response.text().catch(() => "Unknown error") + console.error(`[OllamaCloud] Failed to fetch models: ${response.status} ${response.statusText}`, errorText) + throw new Error(`Failed to fetch models: ${response.status} ${response.statusText} - ${errorText}`) + } + + const data = await response.json() + console.log("[OllamaCloud] Models response:", JSON.stringify(data).substring(0, 500)) + + // Handle different response formats flexibly + const modelsArray = Array.isArray(data.models) ? data.models : + Array.isArray(data) ? data : [] + + // Parse with flexible schema, don't throw on validation failure + // Only include cloud-compatible models (ending in -cloud or known cloud models) + const parsedModels: OllamaModel[] = [] + for (const model of modelsArray) { + try { + const modelName = model.name || model.model || "" + // Filter to only cloud-compatible models + const isCloudModel = modelName.endsWith("-cloud") || + modelName.includes(":cloud") || + modelName.startsWith("gpt-oss") || + modelName.startsWith("qwen3-coder") || + modelName.startsWith("deepseek-v3") + + if (modelName && isCloudModel) { + parsedModels.push({ + name: modelName, + model: model.model || modelName, + size: model.size, + digest: model.digest, + modified_at: model.modified_at, + created_at: model.created_at, + details: model.details + }) + } + } catch (parseError) { + console.warn("[OllamaCloud] Skipping model due to parse error:", model, parseError) + } + } + + console.log(`[OllamaCloud] Parsed ${parsedModels.length} cloud-compatible models`) + return parsedModels + } catch (error) { + console.error("Failed to list Ollama Cloud models:", error) + throw error + } + } + + async chat(request: ChatRequest): Promise> { + if (!this.config.apiKey) { + throw new Error("Ollama Cloud API key is required") + } + + const headers: Record = { + "Content-Type": "application/json" + } + + if (this.config.apiKey) { + headers["Authorization"] = `Bearer ${this.config.apiKey}` + } + + // Inject Context-Engine RAG context if available + let enrichedRequest = request + try { + const contextEngine = getContextEngineService() + if (contextEngine?.isReady()) { + // Get the last user message for context retrieval + const lastUserMessage = [...request.messages].reverse().find(m => m.role === "user") + if (lastUserMessage?.content) { + const contextBlock = await contextEngine.query(lastUserMessage.content, 4096) + if (contextBlock) { + // Clone messages and inject context into the last user message + const messagesWithContext = request.messages.map((msg, index) => { + if (msg === lastUserMessage) { + return { + ...msg, + content: `${contextBlock}\n\n${msg.content}` + } + } + return msg + }) + enrichedRequest = { ...request, messages: messagesWithContext } + console.log("[OllamaCloud] Context-Engine context injected") + } + } + } + } catch (contextError) { + // Graceful fallback - continue without context if Context-Engine fails + console.warn("[OllamaCloud] Context-Engine query failed, continuing without RAG context:", contextError) + } + + try { + const response = await this.makeRequest("/chat", { + method: "POST", + headers, + body: JSON.stringify(enrichedRequest) + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Chat request failed: ${response.statusText} - ${errorText}`) + } + + if (request.stream) { + return this.parseStreamingResponse(response) + } else { + const data = ChatResponseSchema.parse(await response.json()) + return this.createAsyncIterable([data]) + } + } catch (error) { + console.error("Ollama Cloud chat request failed:", error) + throw error + } + } + + async chatWithThinking(request: ChatRequest): Promise> { + const requestWithThinking = { + ...request, + think: true + } + return this.chat(requestWithThinking) + } + + async chatWithStructuredOutput(request: ChatRequest, schema: any): Promise> { + const requestWithFormat = { + ...request, + format: schema + } + return this.chat(requestWithFormat) + } + + async chatWithVision(request: ChatRequest, images: string[]): Promise> { + if (!request.messages.length) { + throw new Error("At least one message is required") + } + + const messagesWithImages = [...request.messages] + const lastUserMessage = messagesWithImages.slice().reverse().find(m => m.role === "user") + + if (lastUserMessage) { + lastUserMessage.images = images + } + + return this.chat({ ...request, messages: messagesWithImages }) + } + + async chatWithTools(request: ChatRequest, tools: ToolDefinition[]): Promise> { + const requestWithTools = { + ...request, + tools + } + return this.chat(requestWithTools) + } + + async chatWithWebSearch(request: ChatRequest): Promise> { + const requestWithWebSearch = { + ...request, + web_search: true + } + return this.chat(requestWithWebSearch) + } + + async generateEmbeddings(request: EmbeddingRequest): Promise { + if (!this.config.apiKey) { + throw new Error("Ollama Cloud API key is required") + } + + const headers: Record = { + "Content-Type": "application/json" + } + + if (this.config.apiKey) { + headers["Authorization"] = `Bearer ${this.config.apiKey}` + } + + try { + const response = await this.makeRequest("/embed", { + method: "POST", + headers, + body: JSON.stringify(request) + }) + + if (!response.ok) { + throw new Error(`Embeddings request failed: ${response.statusText}`) + } + + const data = await response.json() + return EmbeddingResponseSchema.parse(data) + } catch (error) { + console.error("Ollama Cloud embeddings request failed:", error) + throw error + } + } + + async pullModel(modelName: string): Promise { + const headers: Record = { + "Content-Type": "application/json" + } + + if (this.config.apiKey) { + headers["Authorization"] = `Bearer ${this.config.apiKey}` + } + + const response = await this.makeRequest("/pull", { + method: "POST", + headers, + body: JSON.stringify({ name: modelName }) + }) + + if (!response.ok) { + throw new Error(`Failed to pull model ${modelName}: ${response.statusText}`) + } + } + + private async *parseStreamingResponse(response: Response): AsyncIterable { + if (!response.body) { + throw new Error("Response body is missing") + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + const STREAM_TIMEOUT_MS = 60000 // 60 second timeout per chunk + let lastActivity = Date.now() + + const checkTimeout = () => { + if (Date.now() - lastActivity > STREAM_TIMEOUT_MS) { + reader.cancel().catch(() => { }) + throw new Error("Stream timeout - no data received for 60 seconds") + } + } + + try { + while (true) { + checkTimeout() + + // Create a timeout promise + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error("Read timeout")), STREAM_TIMEOUT_MS) + }) + + // Race the read against the timeout + let result: ReadableStreamReadResult + try { + result = await Promise.race([reader.read(), timeoutPromise]) + } catch (timeoutError) { + reader.cancel().catch(() => { }) + throw new Error("Stream read timeout") + } + + const { done, value } = result + if (done) break + + lastActivity = Date.now() + + const lines = decoder.decode(value, { stream: true }).split('\n').filter(line => line.trim()) + + for (const line of lines) { + try { + const data = JSON.parse(line) + const chatResponse = ChatResponseSchema.parse(data) + yield chatResponse + + if (chatResponse.done) { + return + } + } catch (parseError) { + console.warn("Failed to parse streaming line:", line, parseError) + } + } + } + } finally { + reader.releaseLock() + } + } + + private async *createAsyncIterable(items: T[]): AsyncIterable { + for (const item of items) { + yield item + } + } + + private async makeRequest(endpoint: string, options: RequestInit, timeoutMs: number = 120000): Promise { + // Ensure endpoint starts with /api + const apiEndpoint = endpoint.startsWith('/api') ? endpoint : `/api${endpoint}` + const url = `${this.baseUrl}${apiEndpoint}` + + const headers: Record = { + ...options.headers as Record + } + + if (this.config.apiKey) { + headers["Authorization"] = `Bearer ${this.config.apiKey}` + } + + console.log(`[OllamaCloud] Making request to: ${url}`) + + // Add timeout to prevent indefinite hangs + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), timeoutMs) + + try { + return await fetch(url, { + ...options, + headers, + signal: controller.signal + }) + } finally { + clearTimeout(timeoutId) + } + } + + async getCloudModels(): Promise { + const allModels = await this.listModels() + return allModels.filter(model => model.name.endsWith("-cloud")) + } + + static validateApiKey(apiKey: string): boolean { + return typeof apiKey === "string" && apiKey.length > 0 + } + + async getCloudModelNames(): Promise { + const cloudModels = await this.getCloudModels() + return cloudModels.map(model => model.name) + } + + async getThinkingCapableModels(): Promise { + const allModels = await this.listModels() + const thinkingModelPatterns = ["qwen3", "deepseek-r1", "gpt-oss", "deepseek-v3.1"] + return allModels + .map(m => m.name) + .filter(name => thinkingModelPatterns.some(pattern => name.toLowerCase().includes(pattern))) + } + + async getVisionCapableModels(): Promise { + const allModels = await this.listModels() + const visionModelPatterns = ["gemma3", "llama3.2-vision", "llava", "bakllava", "minicpm-v"] + return allModels + .map(m => m.name) + .filter(name => visionModelPatterns.some(pattern => name.toLowerCase().includes(pattern))) + } + + async getEmbeddingModels(): Promise { + const allModels = await this.listModels() + const embeddingModelPatterns = ["embeddinggemma", "qwen3-embedding", "all-minilm", "nomic-embed", "mxbai-embed"] + return allModels + .map(m => m.name) + .filter(name => embeddingModelPatterns.some(pattern => name.toLowerCase().includes(pattern))) + } +} + +export const DEFAULT_CLOUD_MODELS = [ + "gpt-oss:120b-cloud", + "llama3.1:70b-cloud", + "llama3.1:8b-cloud", + "qwen2.5:32b-cloud", + "qwen2.5:7b-cloud" +] as const + +export type CloudModelName = typeof DEFAULT_CLOUD_MODELS[number] + +export const THINKING_MODELS = [ + "qwen3", + "deepseek-r1", + "deepseek-v3.1", + "gpt-oss:120b-cloud" +] as const + +export type ThinkingModelName = typeof THINKING_MODELS[number] + +export const VISION_MODELS = [ + "gemma3", + "llava", + "bakllava", + "minicpm-v" +] as const + +export type VisionModelName = typeof VISION_MODELS[number] + +export const EMBEDDING_MODELS = [ + "embeddinggemma", + "qwen3-embedding", + "all-minilm", + "nomic-embed-text", + "mxbai-embed-large" +] as const + +export type EmbeddingModelName = typeof EMBEDDING_MODELS[number] diff --git a/packages/server/src/integrations/opencode-zen.ts b/packages/server/src/integrations/opencode-zen.ts new file mode 100644 index 0000000..e229c88 --- /dev/null +++ b/packages/server/src/integrations/opencode-zen.ts @@ -0,0 +1,370 @@ +/** + * OpenCode Zen API Integration + * Provides direct access to OpenCode's free "Zen" models without requiring opencode.exe + * Based on reverse-engineering the OpenCode source at https://github.com/sst/opencode + * + * Free models (cost.input === 0) can be accessed with apiKey: "public" + */ + +import { z } from "zod" + +// Configuration schema for OpenCode Zen +export const OpenCodeZenConfigSchema = z.object({ + enabled: z.boolean().default(true), // Free models enabled by default + endpoint: z.string().default("https://opencode.ai/zen/v1"), + apiKey: z.string().optional() +}) + +export type OpenCodeZenConfig = z.infer + +// Model schema matching models.dev format +export const ZenModelSchema = z.object({ + id: z.string(), + name: z.string(), + family: z.string().optional(), + reasoning: z.boolean().optional(), + tool_call: z.boolean().optional(), + attachment: z.boolean().optional(), + temperature: z.boolean().optional(), + cost: z.object({ + input: z.number(), + output: z.number(), + cache_read: z.number().optional(), + cache_write: z.number().optional() + }).optional(), + limit: z.object({ + context: z.number(), + output: z.number() + }).optional() +}) + +export type ZenModel = z.infer + +// Chat message schema (OpenAI-compatible) +export const ChatMessageSchema = z.object({ + role: z.enum(["user", "assistant", "system", "tool"]), + content: z.string().optional(), + tool_calls: z.array(z.object({ + id: z.string(), + type: z.literal("function"), + function: z.object({ + name: z.string(), + arguments: z.string() + }) + })).optional(), + tool_call_id: z.string().optional() +}) + +export type ChatMessage = z.infer + +// Chat request schema +// Tool Definition Schema +export const ToolDefinitionSchema = z.object({ + type: z.literal("function"), + function: z.object({ + name: z.string(), + description: z.string(), + parameters: z.object({ + type: z.literal("object"), + properties: z.record(z.any()), + required: z.array(z.string()).optional() + }) + }) +}) + +export type ToolDefinition = z.infer + +export const ChatRequestSchema = z.object({ + model: z.string(), + messages: z.array(ChatMessageSchema), + stream: z.boolean().default(true), + temperature: z.number().optional(), + max_tokens: z.number().optional(), + tools: z.array(ToolDefinitionSchema).optional(), + tool_choice: z.union([ + z.literal("auto"), + z.literal("none"), + z.object({ + type: z.literal("function"), + function: z.object({ name: z.string() }) + }) + ]).optional(), + workspacePath: z.string().optional(), + enableTools: z.boolean().optional() +}) + +export type ChatRequest = z.infer + +// Chat response chunk schema +export const ChatChunkSchema = z.object({ + id: z.string().optional(), + object: z.string().optional(), + created: z.number().optional(), + model: z.string().optional(), + choices: z.array(z.object({ + index: z.number(), + delta: z.object({ + role: z.string().optional(), + content: z.string().optional() + }).optional(), + message: z.object({ + role: z.string(), + content: z.string() + }).optional(), + finish_reason: z.string().nullable().optional() + })) +}) + +export type ChatChunk = z.infer + +// Known free OpenCode Zen models (cost.input === 0) +// From models.dev API - these are the free tier models +export const FREE_ZEN_MODELS: ZenModel[] = [ + { + id: "gpt-5-nano", + name: "GPT-5 Nano", + family: "gpt-5-nano", + reasoning: true, + tool_call: true, + attachment: true, + temperature: false, + cost: { input: 0, output: 0 }, + limit: { context: 400000, output: 128000 } + }, + { + id: "big-pickle", + name: "Big Pickle", + family: "pickle", + reasoning: false, + tool_call: true, + attachment: false, + temperature: true, + cost: { input: 0, output: 0 }, + limit: { context: 200000, output: 128000 } + }, + { + id: "grok-code", + name: "Grok Code Fast 1", + family: "grok", + reasoning: true, + tool_call: true, + attachment: false, + temperature: true, + cost: { input: 0, output: 0 }, + limit: { context: 256000, output: 256000 } + }, + { + id: "glm-4.7-free", + name: "GLM-4.7", + family: "glm-free", + reasoning: true, + tool_call: true, + attachment: false, + temperature: true, + cost: { input: 0, output: 0 }, + limit: { context: 204800, output: 131072 } + }, + { + id: "alpha-doubao-seed-code", + name: "Doubao Seed Code (alpha)", + family: "doubao", + reasoning: true, + tool_call: true, + attachment: false, + temperature: true, + cost: { input: 0, output: 0 }, + limit: { context: 256000, output: 32000 } + } +] + +export class OpenCodeZenClient { + private config: OpenCodeZenConfig + private baseUrl: string + private modelsCache: ZenModel[] | null = null + private modelsCacheTime: number = 0 + private readonly CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes + + constructor(config?: Partial) { + this.config = OpenCodeZenConfigSchema.parse(config || {}) + this.baseUrl = this.config.endpoint.replace(/\/$/, "") + } + + /** + * Get free Zen models from OpenCode + */ + async getModels(): Promise { + // Return cached models if still valid + const now = Date.now() + if (this.modelsCache && (now - this.modelsCacheTime) < this.CACHE_TTL_MS) { + return this.modelsCache + } + + try { + // Try to fetch fresh models from models.dev + const response = await fetch("https://models.dev/api.json", { + headers: { + "User-Agent": "NomadArch/1.0" + }, + signal: AbortSignal.timeout(10000) + }) + + if (response.ok) { + const data = await response.json() + // Extract OpenCode provider and filter free models + const opencodeProvider = data["opencode"] + if (opencodeProvider && opencodeProvider.models) { + const freeModels: ZenModel[] = [] + for (const [id, model] of Object.entries(opencodeProvider.models)) { + const m = model as any + if (m.cost && m.cost.input === 0) { + freeModels.push({ + id, + name: m.name, + family: m.family, + reasoning: m.reasoning, + tool_call: m.tool_call, + attachment: m.attachment, + temperature: m.temperature, + cost: m.cost, + limit: m.limit + }) + } + } + if (freeModels.length > 0) { + this.modelsCache = freeModels + this.modelsCacheTime = now + return freeModels + } + } + } + } catch (error) { + console.warn("Failed to fetch models from models.dev, using fallback:", error) + } + + // Fallback to hardcoded free models + this.modelsCache = FREE_ZEN_MODELS + this.modelsCacheTime = now + return FREE_ZEN_MODELS + } + + /** + * Test connection to OpenCode Zen API + */ + async testConnection(): Promise { + try { + const models = await this.getModels() + return models.length > 0 + } catch (error) { + console.error("OpenCode Zen connection test failed:", error) + return false + } + } + + /** + * Chat completion (streaming) + */ + async *chatStream(request: ChatRequest): AsyncGenerator { + const headers: Record = { + "Content-Type": "application/json", + "User-Agent": "NomadArch/1.0", + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "NomadArch" + } + if (this.config.apiKey) { + headers["Authorization"] = `Bearer ${this.config.apiKey}` + } + + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers, + body: JSON.stringify({ + ...request, + stream: true + }) + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`OpenCode Zen API error (${response.status}): ${errorText}`) + } + + if (!response.body) { + throw new Error("Response body is missing") + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let buffer = "" + + try { + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split("\n") + buffer = lines.pop() || "" + + for (const line of lines) { + const trimmed = line.trim() + if (trimmed.startsWith("data: ")) { + const data = trimmed.slice(6) + if (data === "[DONE]") return + + try { + const parsed = JSON.parse(data) + yield parsed as ChatChunk + + // Check for finish + if (parsed.choices?.[0]?.finish_reason) { + return + } + } catch (e) { + // Skip invalid JSON + } + } + } + } + } finally { + reader.releaseLock() + } + } + + /** + * Chat completion (non-streaming) + */ + async chat(request: ChatRequest): Promise { + const headers: Record = { + "Content-Type": "application/json", + "User-Agent": "NomadArch/1.0", + "HTTP-Referer": "https://opencode.ai/", + "X-Title": "NomadArch" + } + if (this.config.apiKey) { + headers["Authorization"] = `Bearer ${this.config.apiKey}` + } + + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers, + body: JSON.stringify({ + ...request, + stream: false + }) + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`OpenCode Zen API error (${response.status}): ${errorText}`) + } + + return await response.json() + } +} + +export function getDefaultZenConfig(): OpenCodeZenConfig { + return { + enabled: true, + endpoint: "https://opencode.ai/zen/v1" + } +} diff --git a/packages/server/src/integrations/zai-api.ts b/packages/server/src/integrations/zai-api.ts new file mode 100644 index 0000000..b29ac5c --- /dev/null +++ b/packages/server/src/integrations/zai-api.ts @@ -0,0 +1,309 @@ +import { z } from "zod" +import { createHmac } from "crypto" + +export const ZAIConfigSchema = z.object({ + apiKey: z.string().optional(), + endpoint: z.string().default("https://api.z.ai/api/coding/paas/v4"), + enabled: z.boolean().default(false), + timeout: z.number().default(300000) +}) + +export type ZAIConfig = z.infer + +export const ZAIMessageSchema = z.object({ + role: z.enum(["user", "assistant", "system", "tool"]), + content: z.string().optional(), + tool_calls: z.array(z.object({ + id: z.string(), + type: z.literal("function"), + function: z.object({ + name: z.string(), + arguments: z.string() + }) + })).optional(), + tool_call_id: z.string().optional() +}) + +export type ZAIMessage = z.infer + +// Tool Definition Schema (OpenAI-compatible) +export const ZAIToolSchema = z.object({ + type: z.literal("function"), + function: z.object({ + name: z.string(), + description: z.string(), + parameters: z.object({ + type: z.literal("object"), + properties: z.record(z.object({ + type: z.string(), + description: z.string().optional() + })), + required: z.array(z.string()).optional() + }) + }) +}) + +export type ZAITool = z.infer + +export const ZAIChatRequestSchema = z.object({ + model: z.string().default("glm-4.7"), + messages: z.array(ZAIMessageSchema), + max_tokens: z.number().default(8192), + stream: z.boolean().default(true), + temperature: z.number().optional(), + tools: z.array(ZAIToolSchema).optional(), + tool_choice: z.union([ + z.literal("auto"), + z.literal("none"), + z.object({ + type: z.literal("function"), + function: z.object({ name: z.string() }) + }) + ]).optional(), + thinking: z.object({ + type: z.enum(["enabled", "disabled"]).optional() + }).optional() +}) + +export type ZAIChatRequest = z.infer + +export const ZAIChatResponseSchema = z.object({ + id: z.string(), + object: z.string(), + created: z.number(), + model: z.string(), + choices: z.array(z.object({ + index: z.number(), + message: z.object({ + role: z.string(), + content: z.string().optional().nullable(), + reasoning_content: z.string().optional(), + tool_calls: z.array(z.object({ + id: z.string(), + type: z.literal("function"), + function: z.object({ + name: z.string(), + arguments: z.string() + }) + })).optional() + }), + finish_reason: z.string() + })), + usage: z.object({ + prompt_tokens: z.number(), + completion_tokens: z.number(), + total_tokens: z.number() + }) +}) + +export type ZAIChatResponse = z.infer + +export const ZAIStreamChunkSchema = z.object({ + id: z.string(), + object: z.string(), + created: z.number(), + model: z.string(), + choices: z.array(z.object({ + index: z.number(), + delta: z.object({ + role: z.string().optional(), + content: z.string().optional().nullable(), + reasoning_content: z.string().optional(), + tool_calls: z.array(z.object({ + index: z.number().optional(), + id: z.string().optional(), + type: z.literal("function").optional(), + function: z.object({ + name: z.string().optional(), + arguments: z.string().optional() + }).optional() + })).optional() + }), + finish_reason: z.string().nullable().optional() + })) +}) + +export type ZAIStreamChunk = z.infer + +export const ZAI_MODELS = [ + "glm-4.7", + "glm-4.6", + "glm-4.5", + "glm-4.5-air", + "glm-4.5-flash", + "glm-4.5-long" +] as const + +export type ZAIModelName = typeof ZAI_MODELS[number] + +export class ZAIClient { + private config: ZAIConfig + private baseUrl: string + + constructor(config: ZAIConfig) { + this.config = config + this.baseUrl = config.endpoint.replace(/\/$/, "") + } + + async testConnection(): Promise { + if (!this.config.apiKey) { + return false + } + + try { + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + model: "glm-4.7", + max_tokens: 1, + messages: [{ role: "user", content: "test" }] + }) + }) + + if (!response.ok) { + const text = await response.text() + console.error(`Z.AI connection failed (${response.status}): ${text}`) + } + + return response.ok + } catch (error) { + console.error("Z.AI connection test failed:", error) + return false + } + } + + async listModels(): Promise { + return [...ZAI_MODELS] + } + + async *chatStream(request: ZAIChatRequest): AsyncGenerator { + if (!this.config.apiKey) { + throw new Error("Z.AI API key is required") + } + + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + ...request, + stream: true + }) + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Z.AI API error (${response.status}): ${errorText}`) + } + + if (!response.body) { + throw new Error("Response body is missing") + } + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let buffer = "" + + try { + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split("\n") + buffer = lines.pop() || "" + + for (const line of lines) { + if (line.startsWith("data: ")) { + const data = line.slice(6).trim() + if (data === "[DONE]") return + + try { + const parsed = JSON.parse(data) + yield parsed as ZAIStreamChunk + } catch (e) { + } + } + } + } + } finally { + reader.releaseLock() + } + } + + async chat(request: ZAIChatRequest): Promise { + if (!this.config.apiKey) { + throw new Error("Z.AI API key is required") + } + + const response = await fetch(`${this.baseUrl}/chat/completions`, { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + ...request, + stream: false + }) + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Z.AI API error (${response.status}): ${errorText}`) + } + + return await response.json() + } + + private getHeaders(): Record { + const token = this.generateToken(this.config.apiKey!) + return { + "Content-Type": "application/json", + "Authorization": `Bearer ${token}` + } + } + + private generateToken(apiKey: string, expiresIn: number = 3600): string { + try { + const [id, secret] = apiKey.split(".") + if (!id || !secret) return apiKey // Fallback or handle error + + const now = Date.now() + const payload = { + api_key: id, + exp: now + expiresIn * 1000, + timestamp: now + } + + const header = { + alg: "HS256", + sign_type: "SIGN" + } + + const base64UrlEncode = (obj: any) => { + return Buffer.from(JSON.stringify(obj)) + .toString('base64') + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, '') + } + + const encodedHeader = base64UrlEncode(header) + const encodedPayload = base64UrlEncode(payload) + + const signature = createHmac("sha256", secret) + .update(`${encodedHeader}.${encodedPayload}`) + .digest("base64") + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, '') + + return `${encodedHeader}.${encodedPayload}.${signature}` + } catch (e) { + console.warn("Failed to generate JWT, using raw key", e) + return apiKey + } + } + + static validateApiKey(apiKey: string): boolean { + return typeof apiKey === "string" && apiKey.length > 0 + } +} \ No newline at end of file diff --git a/packages/server/src/launcher.ts b/packages/server/src/launcher.ts new file mode 100644 index 0000000..d28f683 --- /dev/null +++ b/packages/server/src/launcher.ts @@ -0,0 +1,177 @@ +import { spawn } from "child_process" +import os from "os" +import path from "path" +import type { Logger } from "./logger" + +interface BrowserCandidate { + name: string + command: string + args: (url: string) => string[] +} + +const APP_ARGS = (url: string) => [`--app=${url}`, "--new-window"] + +export async function launchInBrowser(url: string, logger: Logger): Promise { + const { platform, candidates, manualExamples } = buildPlatformCandidates(url) + + console.log(`Attempting to launch browser (${platform}) using:`) + candidates.forEach((candidate) => console.log(` - ${candidate.name}: ${candidate.command}`)) + + for (const candidate of candidates) { + const success = await tryLaunch(candidate, url, logger) + if (success) { + return true + } + } + + console.error( + "No supported browser found to launch. Run without --launch and use one of the commands below or install a compatible browser.", + ) + if (manualExamples.length > 0) { + console.error("Manual launch commands:") + manualExamples.forEach((line) => console.error(` ${line}`)) + } + + return false +} + +async function tryLaunch(candidate: BrowserCandidate, url: string, logger: Logger): Promise { + return new Promise((resolve) => { + let resolved = false + try { + const args = candidate.args(url) + const child = spawn(candidate.command, args, { stdio: "ignore", detached: true }) + + child.once("error", (error) => { + if (resolved) return + resolved = true + logger.debug({ err: error, candidate: candidate.name, command: candidate.command, args }, "Browser launch failed") + resolve(false) + }) + + child.once("spawn", () => { + if (resolved) return + resolved = true + logger.info( + { + browser: candidate.name, + command: candidate.command, + args, + fullCommand: [candidate.command, ...args].join(" "), + }, + "Launched browser in app mode", + ) + child.unref() + resolve(true) + }) + } catch (error) { + if (resolved) return + resolved = true + logger.debug({ err: error, candidate: candidate.name, command: candidate.command }, "Browser spawn threw") + resolve(false) + } + }) +} + +function buildPlatformCandidates(url: string) { + switch (os.platform()) { + case "darwin": + return { + platform: "macOS", + candidates: buildMacCandidates(), + manualExamples: buildMacManualExamples(url), + } + case "win32": + return { + platform: "Windows", + candidates: buildWindowsCandidates(), + manualExamples: buildWindowsManualExamples(url), + } + default: + return { + platform: "Linux", + candidates: buildLinuxCandidates(), + manualExamples: buildLinuxManualExamples(url), + } + } +} + +function buildMacCandidates(): BrowserCandidate[] { + const apps = [ + { name: "Google Chrome", path: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" }, + { name: "Google Chrome Canary", path: "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary" }, + { name: "Microsoft Edge", path: "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" }, + { name: "Brave Browser", path: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" }, + { name: "Chromium", path: "/Applications/Chromium.app/Contents/MacOS/Chromium" }, + { name: "Vivaldi", path: "/Applications/Vivaldi.app/Contents/MacOS/Vivaldi" }, + { name: "Arc", path: "/Applications/Arc.app/Contents/MacOS/Arc" }, + ] + + return apps.map((entry) => ({ name: entry.name, command: entry.path, args: APP_ARGS })) +} + +function buildWindowsCandidates(): BrowserCandidate[] { + const programFiles = process.env["ProgramFiles"] + const programFilesX86 = process.env["ProgramFiles(x86)"] + const localAppData = process.env["LocalAppData"] + + const paths = [ + [programFiles, "Google/Chrome/Application/chrome.exe", "Google Chrome"], + [programFilesX86, "Google/Chrome/Application/chrome.exe", "Google Chrome (x86)"], + [localAppData, "Google/Chrome/Application/chrome.exe", "Google Chrome (User)"], + [programFiles, "Microsoft/Edge/Application/msedge.exe", "Microsoft Edge"], + [programFilesX86, "Microsoft/Edge/Application/msedge.exe", "Microsoft Edge (x86)"], + [localAppData, "Microsoft/Edge/Application/msedge.exe", "Microsoft Edge (User)"], + [programFiles, "BraveSoftware/Brave-Browser/Application/brave.exe", "Brave"], + [localAppData, "BraveSoftware/Brave-Browser/Application/brave.exe", "Brave (User)"], + [programFiles, "Chromium/Application/chromium.exe", "Chromium"], + ] as const + + return paths + .filter(([root]) => Boolean(root)) + .map(([root, rel, name]) => ({ + name, + command: path.join(root as string, rel), + args: APP_ARGS, + })) +} + +function buildLinuxCandidates(): BrowserCandidate[] { + const names = [ + "google-chrome", + "google-chrome-stable", + "chromium", + "chromium-browser", + "brave-browser", + "microsoft-edge", + "microsoft-edge-stable", + "vivaldi", + ] + + return names.map((name) => ({ name, command: name, args: APP_ARGS })) +} + +function buildMacManualExamples(url: string) { + return [ + `"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --app="${url}" --new-window`, + `"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" --app="${url}" --new-window`, + `"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" --app="${url}" --new-window`, + ] +} + +function buildWindowsManualExamples(url: string) { + return [ + `"%ProgramFiles%\\Google\\Chrome\\Application\\chrome.exe" --app="${url}" --new-window`, + `"%ProgramFiles%\\Microsoft\\Edge\\Application\\msedge.exe" --app="${url}" --new-window`, + `"%ProgramFiles%\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" --app="${url}" --new-window`, + ] +} + +function buildLinuxManualExamples(url: string) { + return [ + `google-chrome --app="${url}" --new-window`, + `chromium --app="${url}" --new-window`, + `brave-browser --app="${url}" --new-window`, + `microsoft-edge --app="${url}" --new-window`, + ] +} diff --git a/packages/server/src/loader.ts b/packages/server/src/loader.ts new file mode 100644 index 0000000..756d594 --- /dev/null +++ b/packages/server/src/loader.ts @@ -0,0 +1,21 @@ +export async function resolve(specifier: string, context: any, defaultResolve: any) { + try { + return await defaultResolve(specifier, context, defaultResolve) + } catch (error: any) { + if (shouldRetry(specifier, error)) { + const retried = specifier.endsWith(".js") ? specifier : `${specifier}.js` + return defaultResolve(retried, context, defaultResolve) + } + throw error + } +} + +function shouldRetry(specifier: string, error: any) { + if (!error || error.code !== "ERR_MODULE_NOT_FOUND") { + return false + } + if (specifier.startsWith("./") || specifier.startsWith("../")) { + return true + } + return false +} diff --git a/packages/server/src/logger.ts b/packages/server/src/logger.ts new file mode 100644 index 0000000..86a507e --- /dev/null +++ b/packages/server/src/logger.ts @@ -0,0 +1,133 @@ +import { Transform } from "node:stream" +import pino, { Logger as PinoLogger } from "pino" + +export type Logger = PinoLogger + +interface LoggerOptions { + level?: string + destination?: string + component?: string +} + +const LEVEL_LABELS: Record = { + 10: "trace", + 20: "debug", + 30: "info", + 40: "warn", + 50: "error", + 60: "fatal", +} + +const LIFECYCLE_COMPONENTS = new Set(["app", "workspace"]) +const OMITTED_FIELDS = new Set(["time", "msg", "level", "component", "module"]) + +export function createLogger(options: LoggerOptions = {}): Logger { + const level = (options.level ?? process.env.CLI_LOG_LEVEL ?? "info").toLowerCase() + const destination = options.destination ?? process.env.CLI_LOG_DESTINATION ?? "stdout" + const baseComponent = options.component ?? "app" + const loggerOptions = { + level, + base: { component: baseComponent }, + timestamp: false, + } as const + + if (destination && destination !== "stdout") { + const stream = pino.destination({ dest: destination, mkdir: true, sync: false }) + return pino(loggerOptions, stream) + } + + const lifecycleStream = new LifecycleLogStream({ restrictInfoToLifecycle: level === "info" }) + lifecycleStream.pipe(process.stdout) + return pino(loggerOptions, lifecycleStream) +} + +interface LifecycleStreamOptions { + restrictInfoToLifecycle: boolean +} + +class LifecycleLogStream extends Transform { + private buffer = "" + + constructor(private readonly options: LifecycleStreamOptions) { + super() + } + + _transform(chunk: Buffer, _encoding: BufferEncoding, callback: () => void) { + this.buffer += chunk.toString() + let newlineIndex = this.buffer.indexOf("\n") + while (newlineIndex >= 0) { + const line = this.buffer.slice(0, newlineIndex) + this.buffer = this.buffer.slice(newlineIndex + 1) + this.pushFormatted(line) + newlineIndex = this.buffer.indexOf("\n") + } + callback() + } + + _flush(callback: () => void) { + if (this.buffer.length > 0) { + this.pushFormatted(this.buffer) + this.buffer = "" + } + callback() + } + + private pushFormatted(line: string) { + if (!line.trim()) { + return + } + + let entry: Record + try { + entry = JSON.parse(line) + } catch { + return + } + + const levelNumber = typeof entry.level === "number" ? entry.level : 30 + const levelLabel = LEVEL_LABELS[levelNumber] ?? "info" + const component = (entry.component as string | undefined) ?? (entry.module as string | undefined) ?? "app" + + if (this.options.restrictInfoToLifecycle && levelNumber <= 30 && !LIFECYCLE_COMPONENTS.has(component)) { + return + } + + const message = typeof entry.msg === "string" ? entry.msg : "" + const metadata = this.formatMetadata(entry) + const formatted = metadata.length > 0 ? `[${levelLabel.toUpperCase()}] [${component}] ${message} ${metadata}` : `[${levelLabel.toUpperCase()}] [${component}] ${message}` + this.push(`${formatted}\n`) + } + + private formatMetadata(entry: Record): string { + const pairs: string[] = [] + for (const [key, value] of Object.entries(entry)) { + if (OMITTED_FIELDS.has(key)) { + continue + } + + if (key === "err" && value && typeof value === "object") { + const err = value as { type?: string; message?: string; stack?: string } + const errLabel = err.type ?? "Error" + const errMessage = err.message ? `: ${err.message}` : "" + pairs.push(`err=${errLabel}${errMessage}`) + if (err.stack) { + pairs.push(`stack="${err.stack}"`) + } + continue + } + + pairs.push(`${key}=${this.stringifyValue(value)}`) + } + + return pairs.join(" ").trim() + } + + private stringifyValue(value: unknown): string { + if (value === undefined) return "undefined" + if (value === null) return "null" + if (typeof value === "string") return value + if (typeof value === "number" || typeof value === "boolean") return String(value) + if (value instanceof Error) return value.message ?? value.name + return JSON.stringify(value) + } +} diff --git a/packages/server/src/mcp/client.ts b/packages/server/src/mcp/client.ts new file mode 100644 index 0000000..f5741dd --- /dev/null +++ b/packages/server/src/mcp/client.ts @@ -0,0 +1,532 @@ +/** + * MCP Client - Connects to MCP (Model Context Protocol) servers + * and provides tool discovery and execution capabilities. + * + * Supports: + * - stdio-based MCP servers (command + args) + * - HTTP/SSE-based remote MCP servers + */ + +import { spawn, ChildProcess } from "child_process" +import { createLogger } from "../logger" +import path from "path" + +const log = createLogger({ component: "mcp-client" }) + +// MCP Protocol Types +export interface McpServerConfig { + command?: string + args?: string[] + env?: Record + type?: "stdio" | "remote" | "http" | "sse" | "streamable-http" + url?: string + headers?: Record +} + +export interface McpToolDefinition { + name: string + description: string + inputSchema: { + type: "object" + properties: Record + required?: string[] + } +} + +export interface McpToolCall { + name: string + arguments: Record +} + +export interface McpToolResult { + content: Array<{ + type: "text" | "image" | "resource" + text?: string + data?: string + mimeType?: string + }> + isError?: boolean +} + +// MCP JSON-RPC Message Types +interface JsonRpcRequest { + jsonrpc: "2.0" + id: number | string + method: string + params?: unknown +} + +interface JsonRpcResponse { + jsonrpc: "2.0" + id: number | string + result?: unknown + error?: { code: number; message: string; data?: unknown } +} + +/** + * MCP Client for a single server + */ +export class McpClient { + private config: McpServerConfig + private process: ChildProcess | null = null + private messageId = 0 + private pendingRequests: Map void + reject: (reason: unknown) => void + }> = new Map() + private buffer = "" + private tools: McpToolDefinition[] = [] + private connected = false + private serverName: string + + constructor(serverName: string, config: McpServerConfig) { + this.serverName = serverName + this.config = config + } + + /** + * Start and connect to the MCP server + */ + async connect(): Promise { + if (this.connected) return + + if (this.config.type === "remote" || this.config.type === "http" || this.config.type === "sse") { + // HTTP-based server - just mark as connected + this.connected = true + log.info({ server: this.serverName, type: this.config.type }, "Connected to remote MCP server") + return + } + + // Stdio-based server + if (!this.config.command) { + throw new Error(`MCP server ${this.serverName} has no command configured`) + } + + log.info({ server: this.serverName, command: this.config.command, args: this.config.args }, "Starting MCP server") + + this.process = spawn(this.config.command, this.config.args || [], { + stdio: ["pipe", "pipe", "pipe"], + env: { ...process.env, ...this.config.env }, + shell: true + }) + + this.process.stdout?.on("data", (data) => this.handleData(data.toString())) + this.process.stderr?.on("data", (data) => log.warn({ server: this.serverName }, `MCP stderr: ${data}`)) + this.process.on("error", (err) => log.error({ server: this.serverName, error: err }, "MCP process error")) + this.process.on("exit", (code) => { + log.info({ server: this.serverName, code }, "MCP process exited") + this.connected = false + }) + + // Wait for process to start + await new Promise(resolve => setTimeout(resolve, 500)) + + // Initialize the server + try { + await this.sendRequest("initialize", { + protocolVersion: "2024-11-05", + capabilities: { tools: {} }, + clientInfo: { name: "NomadArch", version: "0.4.0" } + }) + + await this.sendRequest("notifications/initialized", {}) + this.connected = true + log.info({ server: this.serverName }, "MCP server initialized") + } catch (error) { + log.error({ server: this.serverName, error }, "Failed to initialize MCP server") + this.disconnect() + throw error + } + } + + /** + * Disconnect from the MCP server + */ + disconnect(): void { + if (this.process) { + this.process.kill() + this.process = null + } + this.connected = false + this.tools = [] + this.pendingRequests.clear() + } + + /** + * List available tools from this MCP server + */ + async listTools(): Promise { + if (!this.connected) { + await this.connect() + } + + if (this.config.type === "remote" || this.config.type === "http") { + // For HTTP servers, fetch tools via HTTP + return this.fetchToolsHttp() + } + + try { + const response = await this.sendRequest("tools/list", {}) as { tools?: McpToolDefinition[] } + this.tools = response.tools || [] + return this.tools + } catch (error) { + log.error({ server: this.serverName, error }, "Failed to list MCP tools") + return [] + } + } + + /** + * Execute a tool on this MCP server + */ + async executeTool(name: string, args: Record): Promise { + if (!this.connected) { + await this.connect() + } + + log.info({ server: this.serverName, tool: name, args }, "Executing MCP tool") + + if (this.config.type === "remote" || this.config.type === "http") { + return this.executeToolHttp(name, args) + } + + try { + const response = await this.sendRequest("tools/call", { name, arguments: args }) as McpToolResult + return response + } catch (error) { + log.error({ server: this.serverName, tool: name, error }, "MCP tool execution failed") + return { + content: [{ type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}` }], + isError: true + } + } + } + + /** + * Send a JSON-RPC request to the MCP server + */ + private async sendRequest(method: string, params?: unknown): Promise { + if (!this.process?.stdin) { + throw new Error("MCP server not running") + } + + const id = ++this.messageId + const request: JsonRpcRequest = { + jsonrpc: "2.0", + id, + method, + params + } + + return new Promise((resolve, reject) => { + this.pendingRequests.set(id, { resolve, reject }) + + const message = JSON.stringify(request) + "\n" + this.process!.stdin!.write(message) + + // Timeout after 30 seconds + setTimeout(() => { + if (this.pendingRequests.has(id)) { + this.pendingRequests.delete(id) + reject(new Error(`MCP request timeout: ${method}`)) + } + }, 30000) + }) + } + + /** + * Handle incoming data from the MCP server + */ + private handleData(data: string): void { + this.buffer += data + const lines = this.buffer.split("\n") + this.buffer = lines.pop() || "" + + for (const line of lines) { + if (!line.trim()) continue + try { + const message = JSON.parse(line) as JsonRpcResponse + if (message.id !== undefined && this.pendingRequests.has(message.id)) { + const pending = this.pendingRequests.get(message.id)! + this.pendingRequests.delete(message.id) + + if (message.error) { + pending.reject(new Error(message.error.message)) + } else { + pending.resolve(message.result) + } + } + } catch (e) { + log.warn({ server: this.serverName }, `Failed to parse MCP message: ${line}`) + } + } + } + + /** + * Fetch tools from HTTP-based MCP server + */ + private async fetchToolsHttp(): Promise { + if (!this.config.url) return [] + + try { + const response = await fetch(`${this.config.url}/tools/list`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...this.config.headers + }, + body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }) + }) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + const data = await response.json() as JsonRpcResponse + const result = data.result as { tools?: McpToolDefinition[] } + return result.tools || [] + } catch (error) { + log.error({ server: this.serverName, error }, "Failed to fetch HTTP MCP tools") + return [] + } + } + + /** + * Execute tool on HTTP-based MCP server + */ + private async executeToolHttp(name: string, args: Record): Promise { + if (!this.config.url) { + return { content: [{ type: "text", text: "No URL configured" }], isError: true } + } + + try { + const response = await fetch(`${this.config.url}/tools/call`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...this.config.headers + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { name, arguments: args } + }) + }) + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`) + } + + const data = await response.json() as JsonRpcResponse + return data.result as McpToolResult + } catch (error) { + return { + content: [{ type: "text", text: `HTTP error: ${error instanceof Error ? error.message : String(error)}` }], + isError: true + } + } + } + + isConnected(): boolean { + return this.connected + } + + getServerName(): string { + return this.serverName + } +} + +/** + * MCP Manager - Manages multiple MCP server connections + */ +export class McpManager { + private clients: Map = new Map() + private configPath: string | null = null + + /** + * Load MCP config from a workspace + */ + async loadConfig(workspacePath: string): Promise { + const configPath = path.join(workspacePath, ".mcp.json") + this.configPath = configPath + + try { + const fs = await import("fs") + if (!fs.existsSync(configPath)) { + log.info({ path: configPath }, "No MCP config found") + return + } + + const content = fs.readFileSync(configPath, "utf-8") + const config = JSON.parse(content) as { mcpServers?: Record } + + if (config.mcpServers) { + for (const [name, serverConfig] of Object.entries(config.mcpServers)) { + this.addServer(name, serverConfig) + } + } + + log.info({ servers: Object.keys(config.mcpServers || {}) }, "Loaded MCP config") + } catch (error) { + log.error({ path: configPath, error }, "Failed to load MCP config") + } + } + + /** + * Add an MCP server + */ + addServer(name: string, config: McpServerConfig): void { + if (this.clients.has(name)) { + this.clients.get(name)!.disconnect() + } + this.clients.set(name, new McpClient(name, config)) + log.info({ server: name }, "Added MCP server") + } + + /** + * Remove an MCP server + */ + removeServer(name: string): void { + const client = this.clients.get(name) + if (client) { + client.disconnect() + this.clients.delete(name) + } + } + + /** + * Get all available tools from all connected servers + */ + async getAllTools(): Promise> { + const allTools: Array = [] + + for (const [name, client] of this.clients) { + try { + const tools = await client.listTools() + for (const tool of tools) { + allTools.push({ ...tool, serverName: name }) + } + } catch (error) { + log.warn({ server: name, error }, "Failed to get tools from MCP server") + } + } + + return allTools + } + + /** + * Convert MCP tools to OpenAI-compatible format + */ + async getToolsAsOpenAIFormat(): Promise> { + const mcpTools = await this.getAllTools() + + return mcpTools.map(tool => ({ + type: "function" as const, + function: { + // Prefix with server name to avoid conflicts + name: `mcp_${tool.serverName}_${tool.name}`, + description: `[MCP: ${tool.serverName}] ${tool.description}`, + parameters: tool.inputSchema + } + })) + } + + /** + * Execute a tool by its full name (mcp_servername_toolname) + */ + async executeTool(fullName: string, args: Record): Promise { + // Parse mcp_servername_toolname format + const match = fullName.match(/^mcp_([^_]+)_(.+)$/) + if (!match) { + return `Error: Invalid MCP tool name format: ${fullName}` + } + + const [, serverName, toolName] = match + const client = this.clients.get(serverName) + + if (!client) { + return `Error: MCP server not found: ${serverName}` + } + + const result = await client.executeTool(toolName, args) + + // Convert result to string + const texts = result.content + .filter(c => c.type === "text" && c.text) + .map(c => c.text!) + + return texts.join("\n") || (result.isError ? "Tool execution failed" : "Tool executed successfully") + } + + /** + * Connect all configured servers + */ + async connectAll(): Promise> { + const results: Record = {} + + for (const [name, client] of this.clients) { + try { + // Add timeout for connection + const connectPromise = client.connect() + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error("Connection timeout")), 15000) + ) + + await Promise.race([connectPromise, timeoutPromise]) + results[name] = { connected: true } + log.info({ server: name }, "MCP server connected successfully") + } catch (error) { + const errorMsg = error instanceof Error ? error.message : String(error) + log.warn({ server: name, error: errorMsg }, "Failed to connect MCP server") + results[name] = { connected: false, error: errorMsg } + } + } + + return results + } + + /** + * Disconnect all servers + */ + disconnectAll(): void { + for (const client of this.clients.values()) { + client.disconnect() + } + this.clients.clear() + } + + /** + * Get status of all servers + */ + getStatus(): Record { + const status: Record = {} + for (const [name, client] of this.clients) { + status[name] = { connected: client.isConnected() } + } + return status + } +} + +// Singleton instance +let globalMcpManager: McpManager | null = null + +export function getMcpManager(): McpManager { + if (!globalMcpManager) { + globalMcpManager = new McpManager() + } + return globalMcpManager +} + +export function resetMcpManager(): void { + if (globalMcpManager) { + globalMcpManager.disconnectAll() + globalMcpManager = null + } +} diff --git a/packages/server/src/mcp/index.ts b/packages/server/src/mcp/index.ts new file mode 100644 index 0000000..051ebc7 --- /dev/null +++ b/packages/server/src/mcp/index.ts @@ -0,0 +1,15 @@ +/** + * MCP Module Index + * Exports MCP client and manager for external MCP server integration. + */ + +export { + McpClient, + McpManager, + getMcpManager, + resetMcpManager, + type McpServerConfig, + type McpToolDefinition, + type McpToolCall, + type McpToolResult +} from "./client" diff --git a/packages/server/src/opencode-config.ts b/packages/server/src/opencode-config.ts new file mode 100644 index 0000000..db0b145 --- /dev/null +++ b/packages/server/src/opencode-config.ts @@ -0,0 +1,60 @@ +import { cpSync, existsSync, mkdirSync, rmSync } from "fs" +import os from "os" +import path from "path" +import { fileURLToPath } from "url" +import { createLogger } from "./logger" +import { getOpencodeWorkspacesRoot, getUserDataRoot } from "./user-data" + +const log = createLogger({ component: "opencode-config" }) +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const devTemplateDir = path.resolve(__dirname, "../../opencode-config") +const prodTemplateDir = path.resolve(__dirname, "opencode-config") + +const isDevBuild = Boolean(process.env.CODENOMAD_DEV ?? process.env.CLI_UI_DEV_SERVER) || existsSync(devTemplateDir) +const templateDir = isDevBuild ? devTemplateDir : prodTemplateDir +const userConfigDir = path.join(getUserDataRoot(), "opencode-config") +const workspaceConfigRoot = getOpencodeWorkspacesRoot() + +export function getOpencodeConfigDir(): string { + if (!existsSync(templateDir)) { + throw new Error(`CodeNomad Opencode config template missing at ${templateDir}`) + } + + if (isDevBuild) { + log.debug({ templateDir }, "Using Opencode config template directly (dev mode)") + return templateDir + } + + refreshUserConfig() + return userConfigDir +} + +export function ensureWorkspaceOpencodeConfig(workspaceId: string): string { + if (!workspaceId) { + return getOpencodeConfigDir() + } + if (!existsSync(templateDir)) { + throw new Error(`CodeNomad Opencode config template missing at ${templateDir}`) + } + + const targetDir = path.join(workspaceConfigRoot, workspaceId) + if (existsSync(targetDir)) { + return targetDir + } + + mkdirSync(path.dirname(targetDir), { recursive: true }) + cpSync(templateDir, targetDir, { recursive: true }) + return targetDir +} + +export function getWorkspaceOpencodeConfigDir(workspaceId: string): string { + return path.join(workspaceConfigRoot, workspaceId) +} + +function refreshUserConfig() { + log.debug({ templateDir, userConfigDir }, "Syncing Opencode config template") + rmSync(userConfigDir, { recursive: true, force: true }) + mkdirSync(path.dirname(userConfigDir), { recursive: true }) + cpSync(templateDir, userConfigDir, { recursive: true }) +} diff --git a/packages/server/src/releases/release-monitor.ts b/packages/server/src/releases/release-monitor.ts new file mode 100644 index 0000000..2fd80c9 --- /dev/null +++ b/packages/server/src/releases/release-monitor.ts @@ -0,0 +1,141 @@ +import { fetch } from "undici" +import type { LatestReleaseInfo } from "../api-types" +import type { Logger } from "../logger" + +const RELEASES_API_URL = "https://api.github.com/repos/NeuralNomadsAI/CodeNomad/releases/latest" +interface ReleaseMonitorOptions { + currentVersion: string + logger: Logger + onUpdate: (release: LatestReleaseInfo | null) => void +} + +interface GithubReleaseResponse { + tag_name?: string + name?: string + html_url?: string + body?: string + published_at?: string + created_at?: string + prerelease?: boolean +} + +interface NormalizedVersion { + major: number + minor: number + patch: number + prerelease: string | null +} + +export interface ReleaseMonitor { + stop(): void +} + +export function startReleaseMonitor(options: ReleaseMonitorOptions): ReleaseMonitor { + let stopped = false + + const refreshRelease = async () => { + if (stopped) return + try { + const release = await fetchLatestRelease(options) + options.onUpdate(release) + } catch (error) { + options.logger.warn({ err: error }, "Failed to refresh release information") + } + } + + void refreshRelease() + + return { + stop() { + stopped = true + }, + } +} + +async function fetchLatestRelease(options: ReleaseMonitorOptions): Promise { + const response = await fetch(RELEASES_API_URL, { + headers: { + Accept: "application/vnd.github+json", + "User-Agent": "CodeNomad-CLI", + }, + }) + + if (!response.ok) { + throw new Error(`Release API responded with ${response.status}`) + } + + const json = (await response.json()) as GithubReleaseResponse + const tagFromServer = json.tag_name || json.name + if (!tagFromServer) { + return null + } + + const normalizedVersion = stripTagPrefix(tagFromServer) + if (!normalizedVersion) { + return null + } + + const current = parseVersion(options.currentVersion) + const remote = parseVersion(normalizedVersion) + + if (compareVersions(remote, current) <= 0) { + return null + } + + return { + version: normalizedVersion, + tag: tagFromServer, + url: json.html_url ?? `https://github.com/NeuralNomadsAI/CodeNomad/releases/tag/${encodeURIComponent(tagFromServer)}`, + channel: json.prerelease || normalizedVersion.includes("-") ? "dev" : "stable", + publishedAt: json.published_at ?? json.created_at, + notes: json.body, + } +} + +function stripTagPrefix(tag: string | undefined): string | null { + if (!tag) return null + const trimmed = tag.trim() + if (!trimmed) return null + return trimmed.replace(/^v/i, "") +} + +function parseVersion(value: string): NormalizedVersion { + const normalized = stripTagPrefix(value) ?? "0.0.0" + const [core, prerelease = null] = normalized.split("-", 2) + const [major = 0, minor = 0, patch = 0] = core.split(".").map((segment) => { + const parsed = Number.parseInt(segment, 10) + return Number.isFinite(parsed) ? parsed : 0 + }) + return { + major, + minor, + patch, + prerelease, + } +} + +function compareVersions(a: NormalizedVersion, b: NormalizedVersion): number { + if (a.major !== b.major) { + return a.major > b.major ? 1 : -1 + } + if (a.minor !== b.minor) { + return a.minor > b.minor ? 1 : -1 + } + if (a.patch !== b.patch) { + return a.patch > b.patch ? 1 : -1 + } + + const aPre = a.prerelease && a.prerelease.length > 0 ? a.prerelease : null + const bPre = b.prerelease && b.prerelease.length > 0 ? b.prerelease : null + + if (aPre === bPre) { + return 0 + } + if (!aPre) { + return 1 + } + if (!bPre) { + return -1 + } + return aPre.localeCompare(bPre) +} diff --git a/packages/server/src/server/http-server.ts b/packages/server/src/server/http-server.ts new file mode 100644 index 0000000..4343c7f --- /dev/null +++ b/packages/server/src/server/http-server.ts @@ -0,0 +1,396 @@ +import Fastify, { type FastifyInstance, type FastifyReply, type FastifyRequest } from "fastify" +import cors from "@fastify/cors" +import fastifyStatic from "@fastify/static" +import replyFrom from "@fastify/reply-from" +import fs from "fs" +import path from "path" +import { fetch } from "undici" +import type { Logger } from "../logger" +import { WorkspaceManager } from "../workspaces/manager" + +import { ConfigStore } from "../config/store" +import { BinaryRegistry } from "../config/binaries" +import { FileSystemBrowser } from "../filesystem/browser" +import { EventBus } from "../events/bus" +import { registerWorkspaceRoutes } from "./routes/workspaces" +import { registerConfigRoutes } from "./routes/config" +import { registerFilesystemRoutes } from "./routes/filesystem" +import { registerMetaRoutes } from "./routes/meta" +import { registerEventRoutes } from "./routes/events" +import { registerStorageRoutes } from "./routes/storage" +import { registerOllamaRoutes } from "./routes/ollama" +import { registerQwenRoutes } from "./routes/qwen" +import { registerZAIRoutes } from "./routes/zai" +import { registerOpenCodeZenRoutes } from "./routes/opencode-zen" +import { registerSkillsRoutes } from "./routes/skills" +import { registerContextEngineRoutes } from "./routes/context-engine" +import { registerNativeSessionsRoutes } from "./routes/native-sessions" +import { initSessionManager } from "../storage/session-store" +import { ServerMeta } from "../api-types" +import { InstanceStore } from "../storage/instance-store" + +interface HttpServerDeps { + host: string + port: number + workspaceManager: WorkspaceManager + configStore: ConfigStore + binaryRegistry: BinaryRegistry + fileSystemBrowser: FileSystemBrowser + eventBus: EventBus + serverMeta: ServerMeta + instanceStore: InstanceStore + uiStaticDir: string + uiDevServerUrl?: string + logger: Logger + dataDir?: string // For session storage +} + +interface HttpServerStartResult { + port: number + url: string + displayHost: string +} + +const DEFAULT_HTTP_PORT = 9898 + +export function createHttpServer(deps: HttpServerDeps) { + const app = Fastify({ logger: false }) + const proxyLogger = deps.logger.child({ component: "proxy" }) + const apiLogger = deps.logger.child({ component: "http" }) + const sseLogger = deps.logger.child({ component: "sse" }) + + // Initialize session manager for Binary-Free Mode + const dataDir = deps.dataDir || path.join(process.cwd(), ".codenomad-data") + initSessionManager(dataDir) + + const sseClients = new Set<() => void>() + const registerSseClient = (cleanup: () => void) => { + sseClients.add(cleanup) + return () => sseClients.delete(cleanup) + } + const closeSseClients = () => { + for (const cleanup of Array.from(sseClients)) { + cleanup() + } + sseClients.clear() + } + + app.addHook("onRequest", (request, _reply, done) => { + ; (request as FastifyRequest & { __logMeta?: { start: bigint } }).__logMeta = { + start: process.hrtime.bigint(), + } + done() + }) + + app.addHook("onResponse", (request, reply, done) => { + const meta = (request as FastifyRequest & { __logMeta?: { start: bigint } }).__logMeta + const durationMs = meta ? Number((process.hrtime.bigint() - meta.start) / BigInt(1_000_000)) : undefined + const base = { + method: request.method, + url: request.url, + status: reply.statusCode, + durationMs, + } + apiLogger.debug(base, "HTTP request completed") + if (apiLogger.isLevelEnabled("trace")) { + apiLogger.trace({ ...base, params: request.params, query: request.query, body: request.body }, "HTTP request payload") + } + done() + }) + + app.register(cors, { + origin: true, + credentials: true, + }) + + app.register(replyFrom, { + contentTypesToEncode: [], + undici: { + connections: 16, + pipelining: 1, + bodyTimeout: 0, + headersTimeout: 0, + }, + }) + + registerWorkspaceRoutes(app, { + workspaceManager: deps.workspaceManager, + instanceStore: deps.instanceStore, + configStore: deps.configStore, + }) + registerConfigRoutes(app, { configStore: deps.configStore, binaryRegistry: deps.binaryRegistry }) + registerFilesystemRoutes(app, { fileSystemBrowser: deps.fileSystemBrowser }) + registerMetaRoutes(app, { serverMeta: deps.serverMeta }) + registerEventRoutes(app, { eventBus: deps.eventBus, registerClient: registerSseClient, logger: sseLogger }) + registerStorageRoutes(app, { + instanceStore: deps.instanceStore, + eventBus: deps.eventBus, + workspaceManager: deps.workspaceManager, + }) + registerOllamaRoutes(app, { logger: deps.logger }) + registerQwenRoutes(app, { logger: deps.logger }) + registerZAIRoutes(app, { logger: deps.logger }) + registerOpenCodeZenRoutes(app, { logger: deps.logger }) + registerSkillsRoutes(app) + registerContextEngineRoutes(app) + + // Register Binary-Free Mode native sessions routes + registerNativeSessionsRoutes(app, { + logger: deps.logger, + workspaceManager: deps.workspaceManager, + dataDir, + eventBus: deps.eventBus, + }) + + registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger }) + + + if (deps.uiDevServerUrl) { + setupDevProxy(app, deps.uiDevServerUrl) + } else { + setupStaticUi(app, deps.uiStaticDir) + } + + return { + instance: app, + start: async (): Promise => { + const attemptListen = async (requestedPort: number) => { + const addressInfo = await app.listen({ port: requestedPort, host: deps.host }) + return { addressInfo, requestedPort } + } + + const autoPortRequested = deps.port === 0 + const primaryPort = autoPortRequested ? DEFAULT_HTTP_PORT : deps.port + + const shouldRetryWithEphemeral = (error: unknown) => { + if (!autoPortRequested) return false + const err = error as NodeJS.ErrnoException | undefined + return Boolean(err && err.code === "EADDRINUSE") + } + + let listenResult + + try { + listenResult = await attemptListen(primaryPort) + } catch (error) { + if (!shouldRetryWithEphemeral(error)) { + throw error + } + deps.logger.warn({ err: error, port: primaryPort }, "Preferred port unavailable, retrying on ephemeral port") + listenResult = await attemptListen(0) + } + + let actualPort = listenResult.requestedPort + + if (typeof listenResult.addressInfo === "string") { + try { + const parsed = new URL(listenResult.addressInfo) + actualPort = Number(parsed.port) || listenResult.requestedPort + } catch { + actualPort = listenResult.requestedPort + } + } else { + const address = app.server.address() + if (typeof address === "object" && address) { + actualPort = address.port + } + } + + const displayHost = deps.host === "0.0.0.0" ? "127.0.0.1" : deps.host === "127.0.0.1" ? "localhost" : deps.host + const serverUrl = `http://${displayHost}:${actualPort}` + + deps.serverMeta.httpBaseUrl = serverUrl + deps.serverMeta.host = deps.host + deps.serverMeta.port = actualPort + deps.serverMeta.listeningMode = deps.host === "0.0.0.0" ? "all" : "local" + deps.logger.info({ port: actualPort, host: deps.host }, "HTTP server listening") + console.log(`CodeNomad Server is ready at ${serverUrl}`) + + return { port: actualPort, url: serverUrl, displayHost } + }, + stop: () => { + closeSseClients() + return app.close() + }, + } +} + +interface InstanceProxyDeps { + workspaceManager: WorkspaceManager + logger: Logger +} + +function registerInstanceProxyRoutes(app: FastifyInstance, deps: InstanceProxyDeps) { + app.register(async (instance) => { + instance.removeAllContentTypeParsers() + instance.addContentTypeParser("*", (req, body, done) => done(null, body)) + + const proxyBaseHandler = async (request: FastifyRequest<{ Params: { id: string } }>, reply: FastifyReply) => { + await proxyWorkspaceRequest({ + request, + reply, + workspaceManager: deps.workspaceManager, + pathSuffix: "", + logger: deps.logger, + }) + } + + const proxyWildcardHandler = async ( + request: FastifyRequest<{ Params: { id: string; "*": string } }>, + reply: FastifyReply, + ) => { + await proxyWorkspaceRequest({ + request, + reply, + workspaceManager: deps.workspaceManager, + pathSuffix: request.params["*"] ?? "", + logger: deps.logger, + }) + } + + instance.all("/workspaces/:id/instance", proxyBaseHandler) + instance.all("/workspaces/:id/instance/*", proxyWildcardHandler) + }) +} + +const INSTANCE_PROXY_HOST = "127.0.0.1" + +async function proxyWorkspaceRequest(args: { + request: FastifyRequest + reply: FastifyReply + workspaceManager: WorkspaceManager + logger: Logger + pathSuffix?: string +}) { + const { request, reply, workspaceManager, logger } = args + const workspaceId = (request.params as { id: string }).id + const workspace = workspaceManager.get(workspaceId) + + if (!workspace) { + reply.code(404).send({ error: "Workspace not found" }) + return + } + + const port = workspaceManager.getInstancePort(workspaceId) + if (!port) { + reply.code(502).send({ error: "Workspace instance is not ready" }) + return + } + + const normalizedSuffix = normalizeInstanceSuffix(args.pathSuffix) + const queryIndex = (request.raw.url ?? "").indexOf("?") + const search = queryIndex >= 0 ? (request.raw.url ?? "").slice(queryIndex) : "" + const targetUrl = `http://${INSTANCE_PROXY_HOST}:${port}${normalizedSuffix}${search}` + + logger.debug({ workspaceId, method: request.method, targetUrl }, "Proxying request to instance") + if (logger.isLevelEnabled("trace")) { + logger.trace({ workspaceId, targetUrl, body: request.body }, "Instance proxy payload") + } + + return reply.from(targetUrl, { + onError: (proxyReply, { error }) => { + logger.error({ err: error, workspaceId, targetUrl }, "Failed to proxy workspace request") + if (!proxyReply.sent) { + proxyReply.code(502).send({ error: "Workspace instance proxy failed" }) + } + }, + }) +} + +function normalizeInstanceSuffix(pathSuffix: string | undefined) { + if (!pathSuffix || pathSuffix === "/") { + return "/" + } + const trimmed = pathSuffix.replace(/^\/+/, "") + return trimmed.length === 0 ? "/" : `/${trimmed}` +} + +function setupStaticUi(app: FastifyInstance, uiDir: string) { + if (!uiDir) { + app.log.warn("UI static directory not provided; API endpoints only") + return + } + + if (!fs.existsSync(uiDir)) { + app.log.warn({ uiDir }, "UI static directory missing; API endpoints only") + return + } + + app.register(fastifyStatic, { + root: uiDir, + prefix: "/", + decorateReply: false, + }) + + const indexPath = path.join(uiDir, "index.html") + + app.setNotFoundHandler((request: FastifyRequest, reply: FastifyReply) => { + const url = request.raw.url ?? "" + if (isApiRequest(url)) { + reply.code(404).send({ message: "Not Found" }) + return + } + + if (fs.existsSync(indexPath)) { + reply.type("text/html").send(fs.readFileSync(indexPath, "utf-8")) + } else { + reply.code(404).send({ message: "UI bundle missing" }) + } + }) +} + +function setupDevProxy(app: FastifyInstance, upstreamBase: string) { + app.log.info({ upstreamBase }, "Proxying UI requests to development server") + app.setNotFoundHandler((request: FastifyRequest, reply: FastifyReply) => { + const url = request.raw.url ?? "" + if (isApiRequest(url)) { + reply.code(404).send({ message: "Not Found" }) + return + } + void proxyToDevServer(request, reply, upstreamBase) + }) +} + +async function proxyToDevServer(request: FastifyRequest, reply: FastifyReply, upstreamBase: string) { + try { + const targetUrl = new URL(request.raw.url ?? "/", upstreamBase) + const response = await fetch(targetUrl, { + method: request.method, + headers: buildProxyHeaders(request.headers), + }) + + response.headers.forEach((value, key) => { + reply.header(key, value) + }) + + reply.code(response.status) + + if (!response.body || request.method === "HEAD") { + reply.send() + return + } + + const buffer = Buffer.from(await response.arrayBuffer()) + reply.send(buffer) + } catch (error) { + request.log.error({ err: error }, "Failed to proxy UI request to dev server") + if (!reply.sent) { + reply.code(502).send("UI dev server is unavailable") + } + } +} + +function isApiRequest(rawUrl: string | null | undefined) { + if (!rawUrl) return false + const pathname = rawUrl.split("?")[0] ?? "" + return pathname === "/api" || pathname.startsWith("/api/") +} + +function buildProxyHeaders(headers: FastifyRequest["headers"]): Record { + const result: Record = {} + for (const [key, value] of Object.entries(headers ?? {})) { + if (!value || key.toLowerCase() === "host") continue + result[key] = Array.isArray(value) ? value.join(",") : value + } + return result +} diff --git a/packages/server/src/server/routes/config.ts b/packages/server/src/server/routes/config.ts new file mode 100644 index 0000000..fed364a --- /dev/null +++ b/packages/server/src/server/routes/config.ts @@ -0,0 +1,62 @@ +import { FastifyInstance } from "fastify" +import { z } from "zod" +import { ConfigStore } from "../../config/store" +import { BinaryRegistry } from "../../config/binaries" +import { ConfigFileSchema } from "../../config/schema" + +interface RouteDeps { + configStore: ConfigStore + binaryRegistry: BinaryRegistry +} + +const BinaryCreateSchema = z.object({ + path: z.string(), + label: z.string().optional(), + makeDefault: z.boolean().optional(), +}) + +const BinaryUpdateSchema = z.object({ + label: z.string().optional(), + makeDefault: z.boolean().optional(), +}) + +const BinaryValidateSchema = z.object({ + path: z.string(), +}) + +export function registerConfigRoutes(app: FastifyInstance, deps: RouteDeps) { + app.get("/api/config/app", async () => deps.configStore.get()) + + app.put("/api/config/app", async (request) => { + const body = ConfigFileSchema.parse(request.body ?? {}) + deps.configStore.replace(body) + return deps.configStore.get() + }) + + app.get("/api/config/binaries", async () => { + return { binaries: deps.binaryRegistry.list() } + }) + + app.post("/api/config/binaries", async (request, reply) => { + const body = BinaryCreateSchema.parse(request.body ?? {}) + const binary = deps.binaryRegistry.create(body) + reply.code(201) + return { binary } + }) + + app.patch<{ Params: { id: string } }>("/api/config/binaries/:id", async (request) => { + const body = BinaryUpdateSchema.parse(request.body ?? {}) + const binary = deps.binaryRegistry.update(request.params.id, body) + return { binary } + }) + + app.delete<{ Params: { id: string } }>("/api/config/binaries/:id", async (request, reply) => { + deps.binaryRegistry.remove(request.params.id) + reply.code(204) + }) + + app.post("/api/config/binaries/validate", async (request) => { + const body = BinaryValidateSchema.parse(request.body ?? {}) + return deps.binaryRegistry.validatePath(body.path) + }) +} diff --git a/packages/server/src/server/routes/context-engine.ts b/packages/server/src/server/routes/context-engine.ts new file mode 100644 index 0000000..b49d4a1 --- /dev/null +++ b/packages/server/src/server/routes/context-engine.ts @@ -0,0 +1,130 @@ +/** + * Context-Engine API routes + * Provides endpoints for querying the Context-Engine status and manually triggering operations. + */ + +import type { FastifyInstance } from "fastify" +import { getContextEngineService } from "../../context-engine" + +export function registerContextEngineRoutes(app: FastifyInstance) { + // Get Context-Engine status + app.get("/api/context-engine/status", async (request, reply) => { + const service = getContextEngineService() + + if (!service) { + return reply.send({ + status: "stopped", + message: "Context-Engine service not initialized" + }) + } + + const status = service.getStatus() + const client = service.getClient() + + // Get more detailed status from the engine if it's running + let details: Record = {} + if (service.isReady()) { + try { + const engineStatus = await client.getStatus() + details = { + indexing: engineStatus.indexing, + indexed_files: engineStatus.indexed_files, + last_indexed: engineStatus.last_indexed + } + } catch { + // Ignore errors, just don't include details + } + } + + return reply.send({ + status, + ready: service.isReady(), + ...details + }) + }) + + // Get Context-Engine health + app.get("/api/context-engine/health", async (request, reply) => { + const service = getContextEngineService() + + if (!service) { + return reply.send({ status: "unhealthy", reason: "Service not initialized" }) + } + + const client = service.getClient() + const health = await client.health() + + return reply.send(health) + }) + + // Manually trigger indexing for a path + app.post("/api/context-engine/index", { + schema: { + body: { + type: "object", + required: ["path"], + properties: { + path: { type: "string" } + } + } + } + }, async (request, reply) => { + const service = getContextEngineService() + + if (!service) { + return reply.status(503).send({ + error: "Context-Engine service not available" + }) + } + + const { path } = request.body as { path: string } + + // Start indexing (non-blocking) + service.indexPath(path).catch(() => { + // Errors are logged internally + }) + + return reply.send({ + status: "started", + message: `Indexing started for: ${path}` + }) + }) + + // Query the Context-Engine + app.post("/api/context-engine/query", { + schema: { + body: { + type: "object", + required: ["query"], + properties: { + query: { type: "string" }, + context_window: { type: "number" } + } + } + } + }, async (request, reply) => { + const service = getContextEngineService() + + if (!service || !service.isReady()) { + return reply.status(503).send({ + error: "Context-Engine not ready", + results: [], + total_results: 0 + }) + } + + const { query, context_window } = request.body as { query: string; context_window?: number } + const client = service.getClient() + + try { + const response = await client.query(query, context_window ?? 4096) + return reply.send(response) + } catch (error) { + return reply.status(500).send({ + error: error instanceof Error ? error.message : "Query failed", + results: [], + total_results: 0 + }) + } + }) +} diff --git a/packages/server/src/server/routes/events.ts b/packages/server/src/server/routes/events.ts new file mode 100644 index 0000000..e8f2329 --- /dev/null +++ b/packages/server/src/server/routes/events.ts @@ -0,0 +1,61 @@ +import { FastifyInstance } from "fastify" +import { EventBus } from "../../events/bus" +import { WorkspaceEventPayload } from "../../api-types" +import { Logger } from "../../logger" + +interface RouteDeps { + eventBus: EventBus + registerClient: (cleanup: () => void) => () => void + logger: Logger +} + +let nextClientId = 0 + +export function registerEventRoutes(app: FastifyInstance, deps: RouteDeps) { + app.get("/api/events", (request, reply) => { + const clientId = ++nextClientId + deps.logger.debug({ clientId }, "SSE client connected") + + const origin = request.headers.origin ?? "*" + reply.raw.setHeader("Access-Control-Allow-Origin", origin) + reply.raw.setHeader("Access-Control-Allow-Credentials", "true") + reply.raw.setHeader("Content-Type", "text/event-stream") + reply.raw.setHeader("Cache-Control", "no-cache") + reply.raw.setHeader("Connection", "keep-alive") + reply.raw.flushHeaders?.() + reply.hijack() + + const send = (event: WorkspaceEventPayload) => { + deps.logger.debug({ clientId, type: event.type }, "SSE event dispatched") + if (deps.logger.isLevelEnabled("trace")) { + deps.logger.trace({ clientId, event }, "SSE event payload") + } + reply.raw.write(`data: ${JSON.stringify(event)}\n\n`) + } + + const unsubscribe = deps.eventBus.onEvent(send) + const heartbeat = setInterval(() => { + reply.raw.write(`:hb ${Date.now()}\n\n`) + }, 15000) + + let closed = false + const close = () => { + if (closed) return + closed = true + clearInterval(heartbeat) + unsubscribe() + reply.raw.end?.() + deps.logger.debug({ clientId }, "SSE client disconnected") + } + + const unregister = deps.registerClient(close) + + const handleClose = () => { + close() + unregister() + } + + request.raw.on("close", handleClose) + request.raw.on("error", handleClose) + }) +} diff --git a/packages/server/src/server/routes/filesystem.ts b/packages/server/src/server/routes/filesystem.ts new file mode 100644 index 0000000..d919c29 --- /dev/null +++ b/packages/server/src/server/routes/filesystem.ts @@ -0,0 +1,27 @@ +import { FastifyInstance } from "fastify" +import { z } from "zod" +import { FileSystemBrowser } from "../../filesystem/browser" + +interface RouteDeps { + fileSystemBrowser: FileSystemBrowser +} + +const FilesystemQuerySchema = z.object({ + path: z.string().optional(), + includeFiles: z.coerce.boolean().optional(), +}) + +export function registerFilesystemRoutes(app: FastifyInstance, deps: RouteDeps) { + app.get("/api/filesystem", async (request, reply) => { + const query = FilesystemQuerySchema.parse(request.query ?? {}) + + try { + return deps.fileSystemBrowser.browse(query.path, { + includeFiles: query.includeFiles, + }) + } catch (error) { + reply.code(400) + return { error: (error as Error).message } + } + }) +} diff --git a/packages/server/src/server/routes/meta.ts b/packages/server/src/server/routes/meta.ts new file mode 100644 index 0000000..87fa690 --- /dev/null +++ b/packages/server/src/server/routes/meta.ts @@ -0,0 +1,157 @@ +import { FastifyInstance } from "fastify" +import os from "os" +import { existsSync } from "fs" +import { NetworkAddress, ServerMeta, PortAvailabilityResponse } from "../../api-types" +import { getAvailablePort } from "../../utils/port" + +interface RouteDeps { + serverMeta: ServerMeta +} + +export interface ModeInfo { + mode: "lite" | "full" + binaryFreeMode: boolean + nativeSessions: boolean + opencodeBinaryAvailable: boolean + providers: { + qwen: boolean + zai: boolean + zen: boolean + } +} + +export function registerMetaRoutes(app: FastifyInstance, deps: RouteDeps) { + app.get("/api/meta", async () => buildMetaResponse(deps.serverMeta)) + + // Mode detection endpoint for Binary-Free Mode + app.get("/api/meta/mode", async (): Promise => { + // Check if any OpenCode binary is available + const opencodePaths = [ + process.env.OPENCODE_PATH, + "opencode", + "opencode.exe", + ].filter(Boolean) as string[] + + let binaryAvailable = false + for (const p of opencodePaths) { + if (existsSync(p)) { + binaryAvailable = true + break + } + } + + // In Binary-Free Mode, we use native session management + const binaryFreeMode = !binaryAvailable + + return { + mode: binaryFreeMode ? "lite" : "full", + binaryFreeMode, + nativeSessions: true, // Native sessions are always available + opencodeBinaryAvailable: binaryAvailable, + providers: { + qwen: true, // Always available + zai: true, // Always available + zen: true, // Always available (needs API key) + } + } + }) + + app.get("/api/ports/available", async () => { + const port = await getAvailablePort(3000) + const response: PortAvailabilityResponse = { port } + return response + }) +} + +function buildMetaResponse(meta: ServerMeta): ServerMeta { + const port = resolvePort(meta) + const addresses = port > 0 ? resolveAddresses(port, meta.host) : [] + + return { + ...meta, + port, + listeningMode: meta.host === "0.0.0.0" ? "all" : "local", + addresses, + } +} + +function resolvePort(meta: ServerMeta): number { + if (Number.isInteger(meta.port) && meta.port > 0) { + return meta.port + } + try { + const parsed = new URL(meta.httpBaseUrl) + const port = Number(parsed.port) + return Number.isInteger(port) && port > 0 ? port : 0 + } catch { + return 0 + } +} + +function resolveAddresses(port: number, host: string): NetworkAddress[] { + const interfaces = os.networkInterfaces() + const seen = new Set() + const results: NetworkAddress[] = [] + + const addAddress = (ip: string, scope: NetworkAddress["scope"]) => { + if (!ip || ip === "0.0.0.0") return + const key = `ipv4-${ip}` + if (seen.has(key)) return + seen.add(key) + results.push({ ip, family: "ipv4", scope, url: `http://${ip}:${port}` }) + } + + const normalizeFamily = (value: string | number) => { + if (typeof value === "string") { + const lowered = value.toLowerCase() + if (lowered === "ipv4") { + return "ipv4" as const + } + } + if (value === 4) return "ipv4" as const + return null + } + + if (host === "0.0.0.0") { + // Enumerate system interfaces (IPv4 only) + for (const entries of Object.values(interfaces)) { + if (!entries) continue + for (const entry of entries) { + const family = normalizeFamily(entry.family) + if (!family) continue + if (!entry.address || entry.address === "0.0.0.0") continue + const scope: NetworkAddress["scope"] = entry.internal ? "loopback" : "external" + addAddress(entry.address, scope) + } + } + } + + // Always include loopback address + addAddress("127.0.0.1", "loopback") + + // Include explicitly configured host if it was IPv4 + if (isIPv4Address(host) && host !== "0.0.0.0") { + const isLoopback = host.startsWith("127.") + addAddress(host, isLoopback ? "loopback" : "external") + } + + const scopeWeight: Record = { external: 0, internal: 1, loopback: 2 } + + return results.sort((a, b) => { + const scopeDelta = scopeWeight[a.scope] - scopeWeight[b.scope] + if (scopeDelta !== 0) return scopeDelta + return a.ip.localeCompare(b.ip) + }) +} + +function isIPv4Address(value: string | undefined): value is string { + if (!value) return false + const parts = value.split(".") + if (parts.length !== 4) return false + return parts.every((part) => { + if (part.length === 0 || part.length > 3) return false + if (!/^[0-9]+$/.test(part)) return false + const num = Number(part) + return Number.isInteger(num) && num >= 0 && num <= 255 + }) +} diff --git a/packages/server/src/server/routes/native-sessions.ts b/packages/server/src/server/routes/native-sessions.ts new file mode 100644 index 0000000..7f1ac83 --- /dev/null +++ b/packages/server/src/server/routes/native-sessions.ts @@ -0,0 +1,629 @@ +/** + * Native Sessions API Routes - Binary-Free Mode + * + * These routes provide session management without requiring the OpenCode binary. + * They're used when running in "Lite Mode" or when OpenCode is unavailable. + */ + +import { FastifyInstance } from "fastify" +import { Logger } from "../../logger" +import { getSessionManager, Session, SessionMessage } from "../../storage/session-store" +import { CORE_TOOLS, executeTools, type ToolCall, type ToolResult } from "../../tools/executor" +import { getMcpManager } from "../../mcp/client" +import { WorkspaceManager } from "../../workspaces/manager" +import { OpenCodeZenClient, ChatMessage } from "../../integrations/opencode-zen" +import { EventBus } from "../../events/bus" + +interface NativeSessionsDeps { + logger: Logger + workspaceManager: WorkspaceManager + dataDir: string + eventBus?: EventBus +} + +// Maximum tool execution loops to prevent infinite loops +const MAX_TOOL_LOOPS = 10 + +export function registerNativeSessionsRoutes(app: FastifyInstance, deps: NativeSessionsDeps) { + const logger = deps.logger.child({ component: "native-sessions" }) + const sessionManager = getSessionManager(deps.dataDir) + + // List all sessions for a workspace + app.get<{ Params: { workspaceId: string } }>("/api/native/workspaces/:workspaceId/sessions", async (request, reply) => { + try { + const sessions = await sessionManager.listSessions(request.params.workspaceId) + return { sessions } + } catch (error) { + logger.error({ error }, "Failed to list sessions") + reply.code(500) + return { error: "Failed to list sessions" } + } + }) + + // Create a new session + app.post<{ + Params: { workspaceId: string } + Body: { title?: string; parentId?: string; model?: { providerId: string; modelId: string }; agent?: string } + }>("/api/native/workspaces/:workspaceId/sessions", async (request, reply) => { + try { + const session = await sessionManager.createSession(request.params.workspaceId, request.body) + + // Emit session created event (using any for custom event type) + if (deps.eventBus) { + deps.eventBus.publish({ + type: "native.session.created", + workspaceId: request.params.workspaceId, + session + } as any) + } + + reply.code(201) + return { session } + } catch (error) { + logger.error({ error }, "Failed to create session") + reply.code(500) + return { error: "Failed to create session" } + } + }) + + // Get a specific session + app.get<{ Params: { workspaceId: string; sessionId: string } }>("/api/native/workspaces/:workspaceId/sessions/:sessionId", async (request, reply) => { + try { + const session = await sessionManager.getSession(request.params.workspaceId, request.params.sessionId) + if (!session) { + reply.code(404) + return { error: "Session not found" } + } + return { session } + } catch (error) { + logger.error({ error }, "Failed to get session") + reply.code(500) + return { error: "Failed to get session" } + } + }) + + // Update a session + app.patch<{ + Params: { workspaceId: string; sessionId: string } + Body: Partial + }>("/api/native/workspaces/:workspaceId/sessions/:sessionId", async (request, reply) => { + try { + const session = await sessionManager.updateSession( + request.params.workspaceId, + request.params.sessionId, + request.body + ) + if (!session) { + reply.code(404) + return { error: "Session not found" } + } + return { session } + } catch (error) { + logger.error({ error }, "Failed to update session") + reply.code(500) + return { error: "Failed to update session" } + } + }) + + // Delete a session + app.delete<{ Params: { workspaceId: string; sessionId: string } }>("/api/native/workspaces/:workspaceId/sessions/:sessionId", async (request, reply) => { + try { + const deleted = await sessionManager.deleteSession(request.params.workspaceId, request.params.sessionId) + if (!deleted) { + reply.code(404) + return { error: "Session not found" } + } + reply.code(204) + return + } catch (error) { + logger.error({ error }, "Failed to delete session") + reply.code(500) + return { error: "Failed to delete session" } + } + }) + + // Get messages for a session + app.get<{ Params: { workspaceId: string; sessionId: string } }>("/api/native/workspaces/:workspaceId/sessions/:sessionId/messages", async (request, reply) => { + try { + const messages = await sessionManager.getSessionMessages( + request.params.workspaceId, + request.params.sessionId + ) + return { messages } + } catch (error) { + logger.error({ error }, "Failed to get messages") + reply.code(500) + return { error: "Failed to get messages" } + } + }) + + // Add a message (user prompt) and get streaming response + app.post<{ + Params: { workspaceId: string; sessionId: string } + Body: { + content: string + provider: "qwen" | "zai" | "zen" + model?: string + accessToken?: string + resourceUrl?: string + enableTools?: boolean + systemPrompt?: string + } + }>("/api/native/workspaces/:workspaceId/sessions/:sessionId/prompt", async (request, reply) => { + const { workspaceId, sessionId } = request.params + const { content, provider, model, accessToken, resourceUrl, enableTools = true, systemPrompt } = request.body + + try { + // Add user message + const userMessage = await sessionManager.addMessage(workspaceId, sessionId, { + role: "user", + content, + status: "completed", + }) + + // Get workspace path + const workspace = deps.workspaceManager.get(workspaceId) + const workspacePath = workspace?.path ?? process.cwd() + + // Get all messages for context + const allMessages = await sessionManager.getSessionMessages(workspaceId, sessionId) + + // Build chat messages array + const chatMessages: ChatMessage[] = [] + + // Add system prompt if provided + if (systemPrompt) { + chatMessages.push({ role: "system", content: systemPrompt }) + } + + // Add conversation history + for (const m of allMessages) { + if (m.role === "user" || m.role === "assistant" || m.role === "system") { + chatMessages.push({ role: m.role, content: m.content ?? "" }) + } + } + + // Load MCP tools + let allTools = [...CORE_TOOLS] + if (enableTools) { + try { + const mcpManager = getMcpManager() + await mcpManager.loadConfig(workspacePath) + const mcpTools = await mcpManager.getToolsAsOpenAIFormat() + allTools = [...CORE_TOOLS, ...mcpTools] + } catch (mcpError) { + logger.warn({ error: mcpError }, "Failed to load MCP tools") + } + } + + // Create streaming response + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'X-Accel-Buffering': 'no', + }) + + // Create assistant message placeholder + const assistantMessage = await sessionManager.addMessage(workspaceId, sessionId, { + role: "assistant", + content: "", + status: "streaming", + }) + + let fullContent = "" + + try { + // Route to the appropriate provider + fullContent = await streamWithProvider({ + provider, + model, + accessToken, + resourceUrl, + messages: chatMessages, + tools: enableTools ? allTools : [], + workspacePath, + rawResponse: reply.raw, + logger, + }) + } catch (streamError) { + logger.error({ error: streamError }, "Stream error") + reply.raw.write(`data: ${JSON.stringify({ error: String(streamError) })}\n\n`) + } + + // Update assistant message with full content + await sessionManager.updateMessage(workspaceId, assistantMessage.id, { + content: fullContent, + status: "completed", + }) + + // Emit message event (using any for custom event type) + if (deps.eventBus) { + deps.eventBus.publish({ + type: "native.message.completed", + workspaceId, + sessionId, + messageId: assistantMessage.id, + } as any) + } + + reply.raw.write('data: [DONE]\n\n') + reply.raw.end() + } catch (error) { + logger.error({ error }, "Failed to process prompt") + if (!reply.sent) { + reply.code(500) + return { error: "Failed to process prompt" } + } + } + }) + + // SSE endpoint for session events + app.get<{ Params: { workspaceId: string } }>("/api/native/workspaces/:workspaceId/events", async (request, reply) => { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + 'X-Accel-Buffering': 'no', + }) + + // Send initial ping + reply.raw.write(`data: ${JSON.stringify({ type: "ping" })}\n\n`) + + // Keep connection alive + const keepAlive = setInterval(() => { + reply.raw.write(`data: ${JSON.stringify({ type: "ping" })}\n\n`) + }, 30000) + + // Handle client disconnect + request.raw.on("close", () => { + clearInterval(keepAlive) + }) + }) + + logger.info("Native sessions routes registered (Binary-Free Mode)") +} + +/** + * Stream chat with the appropriate provider + */ +async function streamWithProvider(opts: { + provider: "qwen" | "zai" | "zen" + model?: string + accessToken?: string + resourceUrl?: string + messages: ChatMessage[] + tools: any[] + workspacePath: string + rawResponse: any + logger: Logger +}): Promise { + const { provider, model, accessToken, resourceUrl, messages, tools, workspacePath, rawResponse, logger } = opts + + let fullContent = "" + let loopCount = 0 + let currentMessages = [...messages] + + // Tool execution loop + while (loopCount < MAX_TOOL_LOOPS) { + loopCount++ + + let responseContent = "" + let toolCalls: ToolCall[] = [] + + // Route to the appropriate provider + switch (provider) { + case "zen": + const zenResult = await streamWithZen(model, currentMessages, tools, rawResponse, logger) + responseContent = zenResult.content + toolCalls = zenResult.toolCalls + break + + case "qwen": + const qwenResult = await streamWithQwen(accessToken, resourceUrl, model, currentMessages, tools, rawResponse, logger) + responseContent = qwenResult.content + toolCalls = qwenResult.toolCalls + break + + case "zai": + const zaiResult = await streamWithZAI(accessToken, model, currentMessages, tools, rawResponse, logger) + responseContent = zaiResult.content + toolCalls = zaiResult.toolCalls + break + } + + fullContent += responseContent + + // If no tool calls, we're done + if (toolCalls.length === 0) { + break + } + + // Execute tools + logger.info({ toolCount: toolCalls.length }, "Executing tool calls") + + // Add assistant message with tool calls + currentMessages.push({ + role: "assistant", + content: responseContent, + tool_calls: toolCalls.map(tc => ({ + id: tc.id, + type: "function" as const, + function: tc.function + })) + }) + + // Execute each tool and add result + const toolResults = await executeTools(workspacePath, toolCalls) + + for (let i = 0; i < toolCalls.length; i++) { + const tc = toolCalls[i] + const result = toolResults[i] + + // Emit tool execution event + rawResponse.write(`data: ${JSON.stringify({ + type: "tool_execution", + tool: tc.function.name, + result: result?.content?.substring(0, 200) // Preview + })}\n\n`) + + currentMessages.push({ + role: "tool", + content: result?.content ?? "Tool execution failed", + tool_call_id: tc.id + }) + } + } + + return fullContent +} + +/** + * Stream with OpenCode Zen (free models) + */ +async function streamWithZen( + model: string | undefined, + messages: ChatMessage[], + tools: any[], + rawResponse: any, + logger: Logger +): Promise<{ content: string; toolCalls: ToolCall[] }> { + const zenClient = new OpenCodeZenClient() + let content = "" + const toolCalls: ToolCall[] = [] + + try { + const stream = zenClient.chatStream({ + model: model ?? "gpt-5-nano", + messages, + stream: true, + tools: tools.length > 0 ? tools : undefined, + tool_choice: tools.length > 0 ? "auto" : undefined, + }) + + for await (const chunk of stream) { + const delta = chunk.choices?.[0]?.delta + if (delta?.content) { + content += delta.content + rawResponse.write(`data: ${JSON.stringify({ choices: [{ delta: { content: delta.content } }] })}\n\n`) + } + + // Handle tool calls (if model supports them) + const deltaToolCalls = (delta as any)?.tool_calls + if (deltaToolCalls) { + for (const tc of deltaToolCalls) { + if (tc.function?.name) { + toolCalls.push({ + id: tc.id, + type: "function", + function: { + name: tc.function.name, + arguments: tc.function.arguments ?? "{}" + } + }) + } + } + } + } + } catch (error) { + logger.error({ error }, "Zen streaming error") + throw error + } + + return { content, toolCalls } +} + +/** + * Stream with Qwen API + */ +async function streamWithQwen( + accessToken: string | undefined, + resourceUrl: string | undefined, + model: string | undefined, + messages: ChatMessage[], + tools: any[], + rawResponse: any, + logger: Logger +): Promise<{ content: string; toolCalls: ToolCall[] }> { + if (!accessToken) { + throw new Error("Qwen access token required. Please authenticate with Qwen first.") + } + + const baseUrl = resourceUrl ?? "https://chat.qwen.ai" + let content = "" + const toolCalls: ToolCall[] = [] + + try { + const response = await fetch(`${baseUrl}/api/v1/chat/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${accessToken}`, + }, + body: JSON.stringify({ + model: model ?? "qwen-plus-latest", + messages, + stream: true, + tools: tools.length > 0 ? tools : undefined, + tool_choice: tools.length > 0 ? "auto" : undefined, + }) + }) + + if (!response.ok) { + const error = await response.text() + throw new Error(`Qwen API error: ${response.status} - ${error}`) + } + + const reader = response.body?.getReader() + if (!reader) throw new Error("No response body") + + const decoder = new TextDecoder() + let buffer = "" + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split("\n") + buffer = lines.pop() ?? "" + + for (const line of lines) { + if (line.startsWith("data: ")) { + const data = line.slice(6) + if (data === "[DONE]") continue + + try { + const parsed = JSON.parse(data) + const delta = parsed.choices?.[0]?.delta + + if (delta?.content) { + content += delta.content + rawResponse.write(`data: ${JSON.stringify({ choices: [{ delta: { content: delta.content } }] })}\n\n`) + } + + if (delta?.tool_calls) { + for (const tc of delta.tool_calls) { + if (tc.function?.name) { + toolCalls.push({ + id: tc.id ?? `call_${Date.now()}`, + type: "function", + function: { + name: tc.function.name, + arguments: tc.function.arguments ?? "{}" + } + }) + } + } + } + } catch { + // Skip invalid JSON + } + } + } + } + } catch (error) { + logger.error({ error }, "Qwen streaming error") + throw error + } + + return { content, toolCalls } +} + +/** + * Stream with Z.AI API + */ +async function streamWithZAI( + accessToken: string | undefined, + model: string | undefined, + messages: ChatMessage[], + tools: any[], + rawResponse: any, + logger: Logger +): Promise<{ content: string; toolCalls: ToolCall[] }> { + let content = "" + const toolCalls: ToolCall[] = [] + + const baseUrl = "https://api.z.ai" + + try { + const headers: Record = { + "Content-Type": "application/json", + } + + if (accessToken) { + headers["Authorization"] = `Bearer ${accessToken}` + } + + const response = await fetch(`${baseUrl}/v1/chat/completions`, { + method: "POST", + headers, + body: JSON.stringify({ + model: model ?? "z1-mini", + messages, + stream: true, + tools: tools.length > 0 ? tools : undefined, + tool_choice: tools.length > 0 ? "auto" : undefined, + }) + }) + + if (!response.ok) { + const error = await response.text() + throw new Error(`Z.AI API error: ${response.status} - ${error}`) + } + + const reader = response.body?.getReader() + if (!reader) throw new Error("No response body") + + const decoder = new TextDecoder() + let buffer = "" + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split("\n") + buffer = lines.pop() ?? "" + + for (const line of lines) { + if (line.startsWith("data: ")) { + const data = line.slice(6) + if (data === "[DONE]") continue + + try { + const parsed = JSON.parse(data) + const delta = parsed.choices?.[0]?.delta + + if (delta?.content) { + content += delta.content + rawResponse.write(`data: ${JSON.stringify({ choices: [{ delta: { content: delta.content } }] })}\n\n`) + } + + if (delta?.tool_calls) { + for (const tc of delta.tool_calls) { + if (tc.function?.name) { + toolCalls.push({ + id: tc.id ?? `call_${Date.now()}`, + type: "function", + function: { + name: tc.function.name, + arguments: tc.function.arguments ?? "{}" + } + }) + } + } + } + } catch { + // Skip invalid JSON + } + } + } + } + } catch (error) { + logger.error({ error }, "Z.AI streaming error") + throw error + } + + return { content, toolCalls } +} diff --git a/packages/server/src/server/routes/ollama.ts b/packages/server/src/server/routes/ollama.ts new file mode 100644 index 0000000..258cd4a --- /dev/null +++ b/packages/server/src/server/routes/ollama.ts @@ -0,0 +1,591 @@ +import { FastifyInstance, FastifyReply } from "fastify" +import { + OllamaCloudClient, + type OllamaCloudConfig, + type ChatRequest, + type EmbeddingRequest, + type ToolDefinition +} from "../../integrations/ollama-cloud" +import { Logger } from "../../logger" +import fs from "fs" +import path from "path" +import { getUserIntegrationsDir } from "../../user-data" + +const CONFIG_DIR = getUserIntegrationsDir() +const CONFIG_FILE = path.join(CONFIG_DIR, "ollama-config.json") + +interface OllamaRouteDeps { + logger: Logger +} + +export async function registerOllamaRoutes( + app: FastifyInstance, + deps: OllamaRouteDeps +) { + const logger = deps.logger.child({ component: "ollama-routes" }) + + app.get('/api/ollama/config', async (request, reply) => { + try { + const config = getOllamaConfig() + return { config: { ...config, apiKey: config.apiKey ? '***' : undefined } } + } catch (error) { + logger.error({ error }, "Failed to get Ollama config") + return reply.status(500).send({ error: "Failed to get Ollama configuration" }) + } + }) + + app.post('/api/ollama/config', { + schema: { + body: { + type: 'object', + required: ['enabled'], + properties: { + enabled: { type: 'boolean' }, + apiKey: { type: 'string' }, + endpoint: { type: 'string' } + } + } + } + }, async (request, reply) => { + try { + const { enabled, apiKey, endpoint } = request.body as any + updateOllamaConfig({ enabled, apiKey, endpoint }) + logger.info("Ollama Cloud configuration updated") + return { success: true, config: { enabled, endpoint, apiKey: apiKey ? '***' : undefined } } + } catch (error) { + logger.error({ error }, "Failed to update Ollama config") + return reply.status(500).send({ error: "Failed to update Ollama configuration" }) + } + }) + + app.post('/api/ollama/test', async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const isConnected = await client.testConnection() + + return { connected: isConnected } + } catch (error) { + logger.error({ error }, "Ollama Cloud connection test failed") + return reply.status(500).send({ error: "Connection test failed" }) + } + }) + + app.get('/api/ollama/models', async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + logger.info({ endpoint: config.endpoint, hasApiKey: !!config.apiKey }, "Fetching Ollama models") + + const client = new OllamaCloudClient(config) + const models = await client.listModels() + + logger.info({ modelCount: models.length }, "Ollama models fetched successfully") + return { models } + } catch (error: any) { + logger.error({ error: error?.message || error }, "Failed to list Ollama models") + return reply.status(500).send({ error: error?.message || "Failed to list models" }) + } + }) + + app.get('/api/ollama/models/cloud', async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const cloudModels = await client.getCloudModels() + + return { models: cloudModels } + } catch (error) { + logger.error({ error }, "Failed to list cloud models") + return reply.status(500).send({ error: "Failed to list cloud models" }) + } + }) + + app.get('/api/ollama/models/thinking', async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const thinkingModels = await client.getThinkingCapableModels() + + return { models: thinkingModels } + } catch (error) { + logger.error({ error }, "Failed to list thinking models") + return reply.status(500).send({ error: "Failed to list thinking models" }) + } + }) + + app.get('/api/ollama/models/vision', async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const visionModels = await client.getVisionCapableModels() + + return { models: visionModels } + } catch (error) { + logger.error({ error }, "Failed to list vision models") + return reply.status(500).send({ error: "Failed to list vision models" }) + } + }) + + app.get('/api/ollama/models/embedding', async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const embeddingModels = await client.getEmbeddingModels() + + return { models: embeddingModels } + } catch (error) { + logger.error({ error }, "Failed to list embedding models") + return reply.status(500).send({ error: "Failed to list embedding models" }) + } + }) + + app.post('/api/ollama/chat', { + schema: { + body: { + type: 'object', + required: ['model', 'messages'], + properties: { + model: { type: 'string' }, + messages: { + type: 'array', + items: { + type: 'object', + required: ['role', 'content'], + properties: { + role: { type: 'string', enum: ['user', 'assistant', 'system'] }, + content: { type: 'string' } + } + } + }, + stream: { type: 'boolean' }, + think: { type: ['boolean', 'string'] }, + format: { type: ['string', 'object'] }, + tools: { type: 'array' }, + web_search: { type: 'boolean' }, + options: { + type: 'object', + properties: { + temperature: { type: 'number', minimum: 0, maximum: 2 }, + top_p: { type: 'number', minimum: 0, maximum: 1 } + } + } + } + } + } + }, async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const chatRequest = request.body as ChatRequest + + if (chatRequest.stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + try { + const stream = await client.chat(chatRequest) + + for await (const chunk of stream) { + reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`) + + if (chunk.done) { + reply.raw.write('data: [DONE]\n\n') + break + } + } + + reply.raw.end() + } catch (streamError: any) { + logger.error({ error: streamError?.message || streamError }, "Ollama streaming failed") + // Send error event to client so it knows the request failed + reply.raw.write(`data: ${JSON.stringify({ error: streamError?.message || "Streaming failed" })}\n\n`) + reply.raw.write('data: [DONE]\n\n') + reply.raw.end() + } + } else { + const stream = await client.chat(chatRequest) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + return chunks[chunks.length - 1] + } + } catch (error) { + logger.error({ error }, "Ollama chat request failed") + return reply.status(500).send({ error: "Chat request failed" }) + } + }) + + app.post('/api/ollama/chat/thinking', { + schema: { + body: { + type: 'object', + required: ['model', 'messages'], + properties: { + model: { type: 'string' }, + messages: { type: 'array' }, + stream: { type: 'boolean' }, + think: { type: ['boolean', 'string'] } + } + } + } + }, async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const chatRequest = request.body as ChatRequest + chatRequest.think = chatRequest.think ?? true + + if (chatRequest.stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + try { + const stream = await client.chatWithThinking(chatRequest) + + for await (const chunk of stream) { + reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`) + + if (chunk.done) { + reply.raw.write('data: [DONE]\n\n') + break + } + } + + reply.raw.end() + } catch (streamError) { + logger.error({ error: streamError }, "Thinking streaming failed") + reply.raw.end() + } + } else { + const stream = await client.chatWithThinking(chatRequest) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + return chunks[chunks.length - 1] + } + } catch (error) { + logger.error({ error }, "Ollama thinking chat request failed") + return reply.status(500).send({ error: "Thinking chat request failed" }) + } + }) + + app.post('/api/ollama/chat/vision', { + schema: { + body: { + type: 'object', + required: ['model', 'messages', 'images'], + properties: { + model: { type: 'string' }, + messages: { type: 'array' }, + images: { type: 'array', items: { type: 'string' } }, + stream: { type: 'boolean' } + } + } + } + }, async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const { model, messages, images, stream } = request.body as any + const chatRequest: ChatRequest = { model, messages, stream: stream ?? false } + + if (chatRequest.stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + try { + const streamResult = await client.chatWithVision(chatRequest, images) + + for await (const chunk of streamResult) { + reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`) + + if (chunk.done) { + reply.raw.write('data: [DONE]\n\n') + break + } + } + + reply.raw.end() + } catch (streamError) { + logger.error({ error: streamError }, "Vision streaming failed") + reply.raw.end() + } + } else { + const streamResult = await client.chatWithVision(chatRequest, images) + const chunks: any[] = [] + for await (const chunk of streamResult) { + chunks.push(chunk) + } + return chunks[chunks.length - 1] + } + } catch (error) { + logger.error({ error }, "Ollama vision chat request failed") + return reply.status(500).send({ error: "Vision chat request failed" }) + } + }) + + app.post('/api/ollama/chat/tools', { + schema: { + body: { + type: 'object', + required: ['model', 'messages', 'tools'], + properties: { + model: { type: 'string' }, + messages: { type: 'array' }, + tools: { type: 'array' }, + stream: { type: 'boolean' } + } + } + } + }, async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const { model, messages, tools, stream } = request.body as any + const chatRequest: ChatRequest = { model, messages, stream: stream ?? false } + + if (chatRequest.stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + try { + const streamResult = await client.chatWithTools(chatRequest, tools) + + for await (const chunk of streamResult) { + reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`) + + if (chunk.done) { + reply.raw.write('data: [DONE]\n\n') + break + } + } + + reply.raw.end() + } catch (streamError) { + logger.error({ error: streamError }, "Tools streaming failed") + reply.raw.end() + } + } else { + const streamResult = await client.chatWithTools(chatRequest, tools) + const chunks: any[] = [] + for await (const chunk of streamResult) { + chunks.push(chunk) + } + return chunks[chunks.length - 1] + } + } catch (error) { + logger.error({ error }, "Ollama tools chat request failed") + return reply.status(500).send({ error: "Tools chat request failed" }) + } + }) + + app.post('/api/ollama/chat/websearch', { + schema: { + body: { + type: 'object', + required: ['model', 'messages'], + properties: { + model: { type: 'string' }, + messages: { type: 'array' }, + stream: { type: 'boolean' } + } + } + } + }, async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const chatRequest = request.body as ChatRequest + + if (chatRequest.stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + try { + const stream = await client.chatWithWebSearch(chatRequest) + + for await (const chunk of stream) { + reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`) + + if (chunk.done) { + reply.raw.write('data: [DONE]\n\n') + break + } + } + + reply.raw.end() + } catch (streamError) { + logger.error({ error: streamError }, "Web search streaming failed") + reply.raw.end() + } + } else { + const stream = await client.chatWithWebSearch(chatRequest) + const chunks: any[] = [] + for await (const chunk of stream) { + chunks.push(chunk) + } + return chunks[chunks.length - 1] + } + } catch (error) { + logger.error({ error }, "Ollama web search chat request failed") + return reply.status(500).send({ error: "Web search chat request failed" }) + } + }) + + app.post('/api/ollama/embeddings', { + schema: { + body: { + type: 'object', + required: ['model', 'input'], + properties: { + model: { type: 'string' }, + input: { oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }] } + } + } + } + }, async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const embedRequest = request.body as EmbeddingRequest + + const result = await client.generateEmbeddings(embedRequest) + return result + } catch (error) { + logger.error({ error }, "Ollama embeddings request failed") + return reply.status(500).send({ error: "Embeddings request failed" }) + } + }) + + app.post('/api/ollama/pull', { + schema: { + body: { + type: 'object', + required: ['model'], + properties: { + model: { type: 'string' } + } + } + } + }, async (request, reply) => { + try { + const config = getOllamaConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Ollama Cloud is not enabled" }) + } + + const client = new OllamaCloudClient(config) + const { model } = request.body as any + + client.pullModel(model).catch(error => { + logger.error({ error, model }, "Failed to pull model") + }) + + return { message: `Started pulling model: ${model}` } + } catch (error) { + logger.error({ error }, "Failed to initiate model pull") + return reply.status(500).send({ error: "Failed to start model pull" }) + } + }) + + logger.info("Ollama Cloud routes registered") +} + +function getOllamaConfig(): OllamaCloudConfig { + try { + if (!fs.existsSync(CONFIG_FILE)) { + return { enabled: false, endpoint: "https://ollama.com" } + } + const data = fs.readFileSync(CONFIG_FILE, 'utf-8') + return JSON.parse(data) + } catch { + return { enabled: false, endpoint: "https://ollama.com" } + } +} + +function updateOllamaConfig(config: Partial): void { + try { + if (!fs.existsSync(CONFIG_DIR)) { + fs.mkdirSync(CONFIG_DIR, { recursive: true }) + } + const current = getOllamaConfig() + + // Only update apiKey if a new non-empty value is provided + const updated = { + ...current, + ...config, + // Preserve existing apiKey if new one is undefined/empty + apiKey: config.apiKey || current.apiKey + } + + fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2)) + console.log(`[Ollama] Config saved: enabled=${updated.enabled}, endpoint=${updated.endpoint}, hasApiKey=${!!updated.apiKey}`) + } catch (error) { + console.error("Failed to save Ollama config:", error) + } +} diff --git a/packages/server/src/server/routes/opencode-zen.ts b/packages/server/src/server/routes/opencode-zen.ts new file mode 100644 index 0000000..c3da638 --- /dev/null +++ b/packages/server/src/server/routes/opencode-zen.ts @@ -0,0 +1,324 @@ +import { FastifyInstance } from "fastify" +import { OpenCodeZenClient, type ChatRequest, getDefaultZenConfig, type ChatMessage } from "../../integrations/opencode-zen" +import { Logger } from "../../logger" +import { CORE_TOOLS, executeTools, type ToolCall, type ToolResult } from "../../tools/executor" +import { getMcpManager } from "../../mcp/client" + +interface OpenCodeZenRouteDeps { + logger: Logger +} + +// Maximum number of tool execution loops +const MAX_TOOL_LOOPS = 10 + +export async function registerOpenCodeZenRoutes( + app: FastifyInstance, + deps: OpenCodeZenRouteDeps +) { + const logger = deps.logger.child({ component: "opencode-zen-routes" }) + + // Create shared client + const client = new OpenCodeZenClient(getDefaultZenConfig()) + + // List available free Zen models + app.get('/api/opencode-zen/models', async (request, reply) => { + try { + const models = await client.getModels() + + return { + models: models.map(m => ({ + id: m.id, + name: m.name, + family: m.family, + provider: "opencode-zen", + free: true, + reasoning: m.reasoning, + tool_call: m.tool_call, + limit: m.limit + })) + } + } catch (error) { + logger.error({ error }, "Failed to list OpenCode Zen models") + return reply.status(500).send({ error: "Failed to list models" }) + } + }) + + // Test connection + app.get('/api/opencode-zen/test', async (request, reply) => { + try { + const connected = await client.testConnection() + return { connected } + } catch (error) { + logger.error({ error }, "OpenCode Zen connection test failed") + return reply.status(500).send({ error: "Connection test failed" }) + } + }) + + // Chat completion endpoint WITH MCP TOOL SUPPORT + app.post('/api/opencode-zen/chat', async (request, reply) => { + try { + const chatRequest = request.body as ChatRequest & { + workspacePath?: string + enableTools?: boolean + } + + // Extract workspace path for tool execution + const workspacePath = chatRequest.workspacePath || process.cwd() + const enableTools = chatRequest.enableTools !== false + + logger.info({ + workspacePath, + receivedWorkspacePath: chatRequest.workspacePath, + enableTools + }, "OpenCode Zen chat request received") + + // Handle streaming with tool loop + if (chatRequest.stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + try { + await streamWithToolLoop( + client, + chatRequest, + workspacePath, + enableTools, + reply.raw, + logger + ) + reply.raw.end() + } catch (streamError) { + logger.error({ error: streamError }, "OpenCode Zen streaming failed") + reply.raw.write(`data: ${JSON.stringify({ error: String(streamError) })}\n\n`) + reply.raw.end() + } + } else { + // Non-streaming with tool loop + const response = await chatWithToolLoop( + client, + chatRequest, + workspacePath, + enableTools, + logger + ) + return response + } + } catch (error) { + logger.error({ error }, "OpenCode Zen chat request failed") + return reply.status(500).send({ error: "Chat request failed" }) + } + }) + + logger.info("OpenCode Zen routes registered with MCP tool support - Free models available!") +} + +/** + * Streaming chat with tool execution loop + */ +async function streamWithToolLoop( + client: OpenCodeZenClient, + request: ChatRequest, + workspacePath: string, + enableTools: boolean, + rawResponse: any, + logger: Logger +): Promise { + let messages = [...request.messages] + let loopCount = 0 + + // Load MCP tools from workspace config + let allTools = [...CORE_TOOLS] + if (enableTools && workspacePath) { + try { + const mcpManager = getMcpManager() + await mcpManager.loadConfig(workspacePath) + const mcpTools = await mcpManager.getToolsAsOpenAIFormat() + allTools = [...CORE_TOOLS, ...mcpTools] + if (mcpTools.length > 0) { + logger.info({ mcpToolCount: mcpTools.length }, "Loaded MCP tools for OpenCode Zen") + } + } catch (mcpError) { + logger.warn({ error: mcpError }, "Failed to load MCP tools") + } + } + + // Inject tools if enabled + const requestWithTools: ChatRequest = { + ...request, + tools: enableTools ? allTools : undefined, + tool_choice: enableTools ? "auto" : undefined + } + + while (loopCount < MAX_TOOL_LOOPS) { + loopCount++ + + // Accumulate tool calls from stream + let accumulatedToolCalls: { [index: number]: { id: string; name: string; arguments: string } } = {} + let hasToolCalls = false + let textContent = "" + + // Stream response + for await (const chunk of client.chatStream({ ...requestWithTools, messages })) { + // Write chunk to client + rawResponse.write(`data: ${JSON.stringify(chunk)}\n\n`) + + const choice = chunk.choices[0] + if (!choice) continue + + // Accumulate text content + if (choice.delta?.content) { + textContent += choice.delta.content + } + + // Accumulate tool calls from delta (if API supports it) + const deltaToolCalls = (choice.delta as any)?.tool_calls + if (deltaToolCalls) { + hasToolCalls = true + for (const tc of deltaToolCalls) { + const idx = tc.index ?? 0 + if (!accumulatedToolCalls[idx]) { + accumulatedToolCalls[idx] = { id: tc.id || "", name: "", arguments: "" } + } + if (tc.id) accumulatedToolCalls[idx].id = tc.id + if (tc.function?.name) accumulatedToolCalls[idx].name += tc.function.name + if (tc.function?.arguments) accumulatedToolCalls[idx].arguments += tc.function.arguments + } + } + + // Check if we should stop + if (choice.finish_reason === "stop") { + rawResponse.write('data: [DONE]\n\n') + return + } + } + + // If no tool calls, we're done + if (!hasToolCalls || !enableTools) { + rawResponse.write('data: [DONE]\n\n') + return + } + + // Convert accumulated tool calls + const toolCalls: ToolCall[] = Object.values(accumulatedToolCalls).map(tc => ({ + id: tc.id, + type: "function" as const, + function: { + name: tc.name, + arguments: tc.arguments + } + })) + + if (toolCalls.length === 0) { + rawResponse.write('data: [DONE]\n\n') + return + } + + logger.info({ toolCalls: toolCalls.map(tc => tc.function.name) }, "Executing tool calls") + + // Add assistant message with tool calls + const assistantMessage: ChatMessage = { + role: "assistant", + content: textContent || undefined, + tool_calls: toolCalls + } + messages.push(assistantMessage) + + // Execute tools + const toolResults = await executeTools(workspacePath, toolCalls) + + // Notify client about tool execution via special event + for (const result of toolResults) { + const toolEvent = { + type: "tool_result", + tool_call_id: result.tool_call_id, + content: result.content + } + rawResponse.write(`data: ${JSON.stringify(toolEvent)}\n\n`) + } + + // Add tool results to messages + for (const result of toolResults) { + const toolMessage: ChatMessage = { + role: "tool", + content: result.content, + tool_call_id: result.tool_call_id + } + messages.push(toolMessage) + } + + logger.info({ loopCount, toolsExecuted: toolResults.length }, "Tool loop iteration complete") + } + + logger.warn({ loopCount }, "Max tool loops reached") + rawResponse.write('data: [DONE]\n\n') +} + +/** + * Non-streaming chat with tool execution loop + */ +async function chatWithToolLoop( + client: OpenCodeZenClient, + request: ChatRequest, + workspacePath: string, + enableTools: boolean, + logger: Logger +): Promise { + let messages = [...request.messages] + let loopCount = 0 + let lastResponse: any = null + + // Inject tools if enabled + const requestWithTools: ChatRequest = { + ...request, + tools: enableTools ? CORE_TOOLS : undefined, + tool_choice: enableTools ? "auto" : undefined + } + + while (loopCount < MAX_TOOL_LOOPS) { + loopCount++ + + const response = await client.chat({ ...requestWithTools, messages, stream: false }) + lastResponse = response + + const choice = response.choices[0] + if (!choice) break + + const toolCalls = (choice.message as any)?.tool_calls + + // If no tool calls, return + if (!toolCalls || toolCalls.length === 0 || !enableTools) { + return response + } + + logger.info({ toolCalls: toolCalls.map((tc: any) => tc.function.name) }, "Executing tool calls") + + // Add assistant message + const assistantMessage: ChatMessage = { + role: "assistant", + content: (choice.message as any).content || undefined, + tool_calls: toolCalls + } + messages.push(assistantMessage) + + // Execute tools + const toolResults = await executeTools(workspacePath, toolCalls) + + // Add tool results + for (const result of toolResults) { + const toolMessage: ChatMessage = { + role: "tool", + content: result.content, + tool_call_id: result.tool_call_id + } + messages.push(toolMessage) + } + + logger.info({ loopCount, toolsExecuted: toolResults.length }, "Tool loop iteration complete") + } + + logger.warn({ loopCount }, "Max tool loops reached") + return lastResponse +} diff --git a/packages/server/src/server/routes/qwen.ts b/packages/server/src/server/routes/qwen.ts new file mode 100644 index 0000000..b4c6fd2 --- /dev/null +++ b/packages/server/src/server/routes/qwen.ts @@ -0,0 +1,478 @@ +import { FastifyInstance, FastifyReply } from "fastify" +import { join } from "path" +import { existsSync, mkdirSync } from "fs" +import { Logger } from "../../logger" +import { CORE_TOOLS, executeTools, type ToolCall, type ToolResult } from "../../tools/executor" +import { getMcpManager } from "../../mcp/client" + +interface QwenRouteDeps { + logger: Logger +} + +const MAX_TOOL_LOOPS = 10 + +const QWEN_OAUTH_BASE_URL = 'https://chat.qwen.ai' +const QWEN_OAUTH_DEVICE_CODE_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/device/code` +const QWEN_OAUTH_TOKEN_ENDPOINT = `${QWEN_OAUTH_BASE_URL}/api/v1/oauth2/token` +const QWEN_OAUTH_CLIENT_ID = 'f0304373b74a44d2b584a3fb70ca9e56' +const QWEN_OAUTH_SCOPE = 'openid profile email model.completion' +const QWEN_OAUTH_DEVICE_GRANT_TYPE = 'urn:ietf:params:oauth:grant-type:device_code' +const QWEN_DEFAULT_RESOURCE_URL = 'https://dashscope.aliyuncs.com/compatible-mode' + +function normalizeQwenModel(model?: string): string { + const raw = (model || "").trim() + if (!raw) return "coder-model" + const lower = raw.toLowerCase() + if (lower === "vision-model" || lower.includes("vision")) return "vision-model" + if (lower === "coder-model") return "coder-model" + if (lower.includes("coder")) return "coder-model" + return "coder-model" +} + +function normalizeQwenResourceUrl(resourceUrl?: string): string { + const raw = typeof resourceUrl === 'string' && resourceUrl.trim().length > 0 + ? resourceUrl.trim() + : QWEN_DEFAULT_RESOURCE_URL + const withProtocol = raw.startsWith('http') ? raw : `https://${raw}` + const trimmed = withProtocol.replace(/\/$/, '') + return trimmed.endsWith('/v1') ? trimmed : `${trimmed}/v1` +} + +export async function registerQwenRoutes( + app: FastifyInstance, + deps: QwenRouteDeps +) { + const logger = deps.logger.child({ component: "qwen-routes" }) + + // Qwen OAuth Device Flow: request device authorization + app.post('/api/qwen/oauth/device', { + schema: { + body: { + type: 'object', + required: ['code_challenge', 'code_challenge_method'], + properties: { + code_challenge: { type: 'string' }, + code_challenge_method: { type: 'string' } + } + } + } + }, async (request, reply) => { + try { + const { code_challenge, code_challenge_method } = request.body as any + const response = await fetch(QWEN_OAUTH_DEVICE_CODE_ENDPOINT, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json' + }, + body: new URLSearchParams({ + client_id: QWEN_OAUTH_CLIENT_ID, + scope: QWEN_OAUTH_SCOPE, + code_challenge, + code_challenge_method + }) + }) + + if (!response.ok) { + const errorText = await response.text() + logger.error({ status: response.status, errorText }, "Qwen device authorization failed") + return reply.status(response.status).send({ error: "Device authorization failed", details: errorText }) + } + + const data = await response.json() + return { ...data } + } catch (error) { + logger.error({ error }, "Failed to request Qwen device authorization") + return reply.status(500).send({ error: "Device authorization failed" }) + } + }) + + // Qwen OAuth Device Flow: poll token endpoint + app.post('/api/qwen/oauth/token', { + schema: { + body: { + type: 'object', + required: ['device_code', 'code_verifier'], + properties: { + device_code: { type: 'string' }, + code_verifier: { type: 'string' } + } + } + } + }, async (request, reply) => { + try { + const { device_code, code_verifier } = request.body as any + const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json' + }, + body: new URLSearchParams({ + grant_type: QWEN_OAUTH_DEVICE_GRANT_TYPE, + client_id: QWEN_OAUTH_CLIENT_ID, + device_code, + code_verifier + }) + }) + + const responseText = await response.text() + if (!response.ok) { + logger.error({ status: response.status, responseText }, "Qwen device token poll failed") + return reply.status(response.status).send(responseText) + } + try { + return reply.send(JSON.parse(responseText)) + } catch { + return reply.send(responseText) + } + } catch (error) { + logger.error({ error }, "Failed to poll Qwen token endpoint") + return reply.status(500).send({ error: "Token polling failed" }) + } + }) + + // Qwen OAuth refresh token + app.post('/api/qwen/oauth/refresh', { + schema: { + body: { + type: 'object', + required: ['refresh_token'], + properties: { + refresh_token: { type: 'string' } + } + } + } + }, async (request, reply) => { + try { + const { refresh_token } = request.body as any + const response = await fetch(QWEN_OAUTH_TOKEN_ENDPOINT, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'application/json' + }, + body: new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token, + client_id: QWEN_OAUTH_CLIENT_ID + }) + }) + + const responseText = await response.text() + if (!response.ok) { + logger.error({ status: response.status, responseText }, "Qwen token refresh failed") + return reply.status(response.status).send(responseText) + } + + try { + return reply.send(JSON.parse(responseText)) + } catch { + return reply.send(responseText) + } + } catch (error) { + logger.error({ error }, "Failed to refresh Qwen token") + return reply.status(500).send({ error: "Token refresh failed" }) + } + }) + + // Get user info + app.get('/api/qwen/user', async (request, reply) => { + try { + const authHeader = request.headers.authorization + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return reply.status(401).send({ error: "Authorization required" }) + } + + const token = authHeader.substring(7) + const userResponse = await fetch('https://chat.qwen.ai/api/v1/user', { + headers: { + 'Authorization': `Bearer ${token}` + } + }) + + if (!userResponse.ok) { + return reply.status(401).send({ error: "Invalid token" }) + } + + const userData = await userResponse.json() + return { user: userData } + } catch (error) { + logger.error({ error }, "Failed to fetch Qwen user info") + return reply.status(500).send({ error: "Failed to fetch user info" }) + } + }) + + /** + * Streaming chat with tool execution loop for Qwen + */ + async function streamWithToolLoop( + accessToken: string, + chatUrl: string, + initialRequest: any, + workspacePath: string, + enableTools: boolean, + rawResponse: any, + logger: Logger + ) { + let messages = [...initialRequest.messages] + let loopCount = 0 + const model = initialRequest.model + + while (loopCount < MAX_TOOL_LOOPS) { + loopCount++ + logger.info({ loopCount, model }, "Starting Qwen tool loop iteration") + + const response = await fetch(chatUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${accessToken}`, + 'Accept': 'text/event-stream' + }, + body: JSON.stringify({ + ...initialRequest, + messages, + stream: true, + tools: enableTools ? initialRequest.tools : undefined, + tool_choice: enableTools ? "auto" : undefined + }) + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Qwen API error (${response.status}): ${errorText}`) + } + + if (!response.body) throw new Error("No response body") + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let textContent = "" + let hasToolCalls = false + let accumulatedToolCalls: Record = {} + let buffer = "" + + while (true) { + const { done, value } = await reader.read() + if (done) break + buffer += decoder.decode(value, { stream: true }) + const lines = buffer.split("\n") + buffer = lines.pop() || "" + + for (const line of lines) { + const trimmed = line.trim() + if (!trimmed.startsWith("data: ")) continue + const data = trimmed.slice(6).trim() + if (data === "[DONE]") { + if (!hasToolCalls) { + rawResponse.write('data: [DONE]\n\n') + return + } + break + } + + let chunk: any + try { + chunk = JSON.parse(data) + } catch (e) { + continue + } + + const choice = chunk.choices?.[0] + if (!choice) continue + + // Pass through text content to client + if (choice.delta?.content) { + textContent += choice.delta.content + rawResponse.write(`data: ${JSON.stringify(chunk)}\n\n`) + } + + // Accumulate tool calls + if (choice.delta?.tool_calls) { + hasToolCalls = true + for (const tc of choice.delta.tool_calls) { + const idx = tc.index ?? 0 + if (!accumulatedToolCalls[idx]) { + accumulatedToolCalls[idx] = { id: tc.id || "", name: "", arguments: "" } + } + if (tc.id) accumulatedToolCalls[idx].id = tc.id + if (tc.function?.name) accumulatedToolCalls[idx].name += tc.function.name + if (tc.function?.arguments) accumulatedToolCalls[idx].arguments += tc.function.arguments + } + } + + if (choice.finish_reason === "tool_calls") { + break + } + + if (choice.finish_reason === "stop" && !hasToolCalls) { + rawResponse.write('data: [DONE]\n\n') + return + } + } + } + + // If no tool calls, we're done + if (!hasToolCalls || !enableTools) { + rawResponse.write('data: [DONE]\n\n') + return + } + + // Execute tools + const toolCalls: ToolCall[] = Object.values(accumulatedToolCalls).map(tc => ({ + id: tc.id, + type: "function" as const, + function: { name: tc.name, arguments: tc.arguments } + })) + + logger.info({ toolCalls: toolCalls.map(tc => tc.function.name) }, "Executing Qwen tool calls") + + messages.push({ + role: "assistant", + content: textContent || undefined, + tool_calls: toolCalls + }) + + const toolResults = await executeTools(workspacePath, toolCalls) + + // Notify frontend + for (const result of toolResults) { + const toolEvent = { + type: "tool_result", + tool_call_id: result.tool_call_id, + content: result.content + } + rawResponse.write(`data: ${JSON.stringify(toolEvent)}\n\n`) + messages.push({ + role: "tool", + content: result.content, + tool_call_id: result.tool_call_id + }) + } + } + + rawResponse.write('data: [DONE]\n\n') + } + + // Qwen Chat API - with tool support + app.post('/api/qwen/chat', { + schema: { + body: { + type: 'object', + required: ['model', 'messages'], + properties: { + model: { type: 'string' }, + messages: { type: 'array' }, + stream: { type: 'boolean' }, + resource_url: { type: 'string' }, + workspacePath: { type: 'string' }, + enableTools: { type: 'boolean' } + } + } + } + }, async (request, reply) => { + try { + const authHeader = request.headers.authorization + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return reply.status(401).send({ error: "Authorization required" }) + } + + const accessToken = authHeader.substring(7) + const { model, messages, stream, resource_url, workspacePath, enableTools } = request.body as any + + const apiBaseUrl = normalizeQwenResourceUrl(resource_url) + const normalizedModel = normalizeQwenModel(model) + const chatUrl = `${apiBaseUrl}/chat/completions` + + // MCP Tool Loading + let allTools = [...CORE_TOOLS] + const effectiveWorkspacePath = workspacePath || process.cwd() + const toolsEnabled = enableTools !== false + + if (toolsEnabled && effectiveWorkspacePath) { + try { + const mcpManager = getMcpManager() + await mcpManager.loadConfig(effectiveWorkspacePath) + const mcpTools = await mcpManager.getToolsAsOpenAIFormat() + allTools = [...CORE_TOOLS, ...mcpTools] + } catch (mcpError) { + logger.warn({ error: mcpError }, "Failed to load MCP tools for Qwen") + } + } + + logger.info({ chatUrl, model: normalizedModel, tools: allTools.length }, "Proxying Qwen chat with tools") + + if (stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + await streamWithToolLoop( + accessToken, + chatUrl, + { model: normalizedModel, messages, tools: allTools }, + effectiveWorkspacePath, + toolsEnabled, + reply.raw, + logger + ) + } else { + const response = await fetch(chatUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${accessToken}` + }, + body: JSON.stringify({ + model: normalizedModel, + messages, + stream: false + }) + }) + const data = await response.json() + return reply.send(data) + } + } catch (error) { + logger.error({ error }, "Qwen chat proxy failed") + return reply.status(500).send({ error: "Chat request failed" }) + } + }) + + // Qwen Models list endpoint + app.get('/api/qwen/models', async (request, reply) => { + try { + const authHeader = request.headers.authorization + if (!authHeader || !authHeader.startsWith('Bearer ')) { + return reply.status(401).send({ error: "Authorization required" }) + } + + const accessToken = authHeader.substring(7) + const resourceUrl = (request.query as any).resource_url || 'https://chat.qwen.ai' + const modelsUrl = `${resourceUrl}/api/v1/models` + + const response = await fetch(modelsUrl, { + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Accept': 'application/json' + } + }) + + if (!response.ok) { + const errorText = await response.text() + logger.error({ status: response.status, errorText }, "Qwen models request failed") + return reply.status(response.status).send({ error: "Models request failed", details: errorText }) + } + + const data = await response.json() + return reply.send(data) + } catch (error) { + logger.error({ error }, "Qwen models request failed") + return reply.status(500).send({ error: "Models request failed" }) + } + }) + + logger.info("Qwen OAuth routes registered") +} diff --git a/packages/server/src/server/routes/skills.ts b/packages/server/src/server/routes/skills.ts new file mode 100644 index 0000000..989964c --- /dev/null +++ b/packages/server/src/server/routes/skills.ts @@ -0,0 +1,141 @@ +import { FastifyInstance } from "fastify" +import { z } from "zod" +import type { SkillCatalogResponse, SkillDetail, SkillDescriptor } from "../../api-types" + +const SKILLS_OWNER = "anthropics" +const SKILLS_REPO = "skills" +const SKILLS_BRANCH = "main" +const SKILLS_ROOT = "skills" +const CATALOG_TTL_MS = 30 * 60 * 1000 +const DETAIL_TTL_MS = 30 * 60 * 1000 + +type CachedCatalog = { skills: SkillDescriptor[]; fetchedAt: number } +type CachedDetail = { detail: SkillDetail; fetchedAt: number } + +let catalogCache: CachedCatalog | null = null +const detailCache = new Map() + +interface RepoEntry { + name: string + path: string + type: "file" | "dir" +} + +function parseFrontmatter(markdown: string): { attributes: Record; body: string } { + if (!markdown.startsWith("---")) { + return { attributes: {}, body: markdown.trim() } + } + const end = markdown.indexOf("\n---", 3) + if (end === -1) { + return { attributes: {}, body: markdown.trim() } + } + const frontmatter = markdown.slice(3, end).trim() + const body = markdown.slice(end + 4).trimStart() + const attributes: Record = {} + for (const line of frontmatter.split(/\r?\n/)) { + const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/) + if (!match) continue + const key = match[1].trim() + const value = match[2]?.trim() ?? "" + attributes[key] = value + } + return { attributes, body } +} + +async function fetchJson(url: string): Promise { + const response = await fetch(url, { + headers: { "User-Agent": "NomadArch-Skills" }, + }) + if (!response.ok) { + const message = await response.text().catch(() => "") + throw new Error(message || `Request failed (${response.status})`) + } + return (await response.json()) as T +} + +async function fetchText(url: string): Promise { + const response = await fetch(url, { + headers: { "User-Agent": "NomadArch-Skills" }, + }) + if (!response.ok) { + const message = await response.text().catch(() => "") + throw new Error(message || `Request failed (${response.status})`) + } + return await response.text() +} + +async function fetchCatalog(): Promise { + const url = `https://api.github.com/repos/${SKILLS_OWNER}/${SKILLS_REPO}/contents/${SKILLS_ROOT}?ref=${SKILLS_BRANCH}` + const entries = await fetchJson(url) + const directories = entries.filter((entry) => entry.type === "dir") + const results: SkillDescriptor[] = [] + + for (const dir of directories) { + try { + const skill = await fetchSkillDetail(dir.name) + results.push({ id: skill.id, name: skill.name, description: skill.description }) + } catch { + results.push({ id: dir.name, name: dir.name, description: "" }) + } + } + + return results +} + +async function fetchSkillDetail(id: string): Promise { + const markdownUrl = `https://raw.githubusercontent.com/${SKILLS_OWNER}/${SKILLS_REPO}/${SKILLS_BRANCH}/${SKILLS_ROOT}/${id}/SKILL.md` + const markdown = await fetchText(markdownUrl) + const parsed = parseFrontmatter(markdown) + const name = parsed.attributes.name || id + const description = parsed.attributes.description || "" + return { + id, + name, + description, + content: parsed.body.trim(), + } +} + +async function getCatalogCached(): Promise { + const now = Date.now() + if (catalogCache && now - catalogCache.fetchedAt < CATALOG_TTL_MS) { + return catalogCache.skills + } + const skills = await fetchCatalog() + catalogCache = { skills, fetchedAt: now } + return skills +} + +async function getDetailCached(id: string): Promise { + const now = Date.now() + const cached = detailCache.get(id) + if (cached && now - cached.fetchedAt < DETAIL_TTL_MS) { + return cached.detail + } + const detail = await fetchSkillDetail(id) + detailCache.set(id, { detail, fetchedAt: now }) + return detail +} + +export async function registerSkillsRoutes(app: FastifyInstance) { + app.get("/api/skills/catalog", async (): Promise => { + const skills = await getCatalogCached() + return { skills } + }) + + app.get<{ Querystring: { id?: string } }>("/api/skills/detail", async (request, reply): Promise => { + const query = z.object({ id: z.string().min(1) }).parse(request.query ?? {}) + try { + return await getDetailCached(query.id) + } catch (error) { + request.log.error({ err: error, skillId: query.id }, "Failed to fetch skill detail") + reply.code(502) + return { + id: query.id, + name: query.id, + description: "", + content: "Unable to load skill content.", + } + } + }) +} diff --git a/packages/server/src/server/routes/storage.ts b/packages/server/src/server/routes/storage.ts new file mode 100644 index 0000000..60e492e --- /dev/null +++ b/packages/server/src/server/routes/storage.ts @@ -0,0 +1,93 @@ +import { FastifyInstance } from "fastify" +import { z } from "zod" +import { InstanceStore } from "../../storage/instance-store" +import { EventBus } from "../../events/bus" +import { ModelPreferenceSchema } from "../../config/schema" +import type { InstanceData, Task, SessionTasks } from "../../api-types" +import { WorkspaceManager } from "../../workspaces/manager" + +interface RouteDeps { + instanceStore: InstanceStore + eventBus: EventBus + workspaceManager: WorkspaceManager +} + +const TaskSchema = z.object({ + id: z.string(), + title: z.string(), + status: z.enum(["completed", "interrupted", "in-progress", "pending"]), + timestamp: z.number(), + messageIds: z.array(z.string()).optional(), +}) + +const InstanceDataSchema = z.object({ + messageHistory: z.array(z.string()).default([]), + agentModelSelections: z.record(z.string(), ModelPreferenceSchema).default({}), + sessionTasks: z.record(z.string(), z.array(TaskSchema)).optional(), + sessionSkills: z + .record( + z.string(), + z.array(z.object({ id: z.string(), name: z.string(), description: z.string().optional() })), + ) + .optional(), + customAgents: z + .array( + z.object({ + name: z.string(), + description: z.string().optional(), + prompt: z.string(), + }), + ) + .optional(), +}) + +const EMPTY_INSTANCE_DATA: InstanceData = { + messageHistory: [], + agentModelSelections: {}, + sessionTasks: {}, + sessionSkills: {}, + customAgents: [], +} + +export function registerStorageRoutes(app: FastifyInstance, deps: RouteDeps) { + const resolveStorageKey = (instanceId: string): string => { + const workspace = deps.workspaceManager.get(instanceId) + return workspace?.path ?? instanceId + } + + app.get<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => { + try { + const storageId = resolveStorageKey(request.params.id) + const data = await deps.instanceStore.read(storageId) + return data + } catch (error) { + reply.code(500) + return { error: error instanceof Error ? error.message : "Failed to read instance data" } + } + }) + + app.put<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => { + try { + const body = InstanceDataSchema.parse(request.body ?? {}) + const storageId = resolveStorageKey(request.params.id) + await deps.instanceStore.write(storageId, body) + deps.eventBus.publish({ type: "instance.dataChanged", instanceId: request.params.id, data: body }) + reply.code(204) + } catch (error) { + reply.code(400) + return { error: error instanceof Error ? error.message : "Failed to save instance data" } + } + }) + + app.delete<{ Params: { id: string } }>("/api/storage/instances/:id", async (request, reply) => { + try { + const storageId = resolveStorageKey(request.params.id) + await deps.instanceStore.delete(storageId) + deps.eventBus.publish({ type: "instance.dataChanged", instanceId: request.params.id, data: EMPTY_INSTANCE_DATA }) + reply.code(204) + } catch (error) { + reply.code(500) + return { error: error instanceof Error ? error.message : "Failed to delete instance data" } + } + }) +} diff --git a/packages/server/src/server/routes/workspaces.ts b/packages/server/src/server/routes/workspaces.ts new file mode 100644 index 0000000..aadfb20 --- /dev/null +++ b/packages/server/src/server/routes/workspaces.ts @@ -0,0 +1,487 @@ +import { FastifyInstance, FastifyReply } from "fastify" +import { spawnSync } from "child_process" +import { z } from "zod" +import { existsSync, mkdirSync } from "fs" +import { cp, readFile, writeFile, stat as readFileStat } from "fs/promises" +import path from "path" +import { WorkspaceManager } from "../../workspaces/manager" +import { InstanceStore } from "../../storage/instance-store" +import { ConfigStore } from "../../config/store" +import { getWorkspaceOpencodeConfigDir } from "../../opencode-config" + +interface RouteDeps { + workspaceManager: WorkspaceManager + instanceStore: InstanceStore + configStore: ConfigStore +} + +const WorkspaceCreateSchema = z.object({ + path: z.string(), + name: z.string().optional(), +}) + +const WorkspaceFilesQuerySchema = z.object({ + path: z.string().optional(), +}) + +const WorkspaceFileContentQuerySchema = z.object({ + path: z.string(), +}) + +const WorkspaceFileSearchQuerySchema = z.object({ + q: z.string().trim().min(1, "Query is required"), + limit: z.coerce.number().int().positive().max(200).optional(), + type: z.enum(["all", "file", "directory"]).optional(), + refresh: z + .string() + .optional() + .transform((value) => (value === undefined ? undefined : value === "true")), +}) + +export function registerWorkspaceRoutes(app: FastifyInstance, deps: RouteDeps) { + app.get("/api/workspaces", async () => { + return deps.workspaceManager.list() + }) + + app.post("/api/workspaces", async (request, reply) => { + try { + const body = WorkspaceCreateSchema.parse(request.body ?? {}) + const workspace = await deps.workspaceManager.create(body.path, body.name) + reply.code(201) + return workspace + } catch (error) { + request.log.error({ err: error }, "Failed to create workspace") + const message = error instanceof Error ? error.message : "Failed to create workspace" + reply.code(400).type("text/plain").send(message) + } + }) + + app.get<{ Params: { id: string } }>("/api/workspaces/:id", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + return workspace + }) + + app.delete<{ Params: { id: string } }>("/api/workspaces/:id", async (request, reply) => { + await deps.workspaceManager.delete(request.params.id) + reply.code(204) + }) + + app.get<{ + Params: { id: string } + Querystring: { path?: string } + }>("/api/workspaces/:id/files", async (request, reply) => { + try { + const query = WorkspaceFilesQuerySchema.parse(request.query ?? {}) + return deps.workspaceManager.listFiles(request.params.id, query.path ?? ".") + } catch (error) { + return handleWorkspaceError(error, reply) + } + }) + + app.get<{ + Params: { id: string } + Querystring: { q?: string; limit?: string; type?: "all" | "file" | "directory"; refresh?: string } + }>("/api/workspaces/:id/files/search", async (request, reply) => { + try { + const query = WorkspaceFileSearchQuerySchema.parse(request.query ?? {}) + return deps.workspaceManager.searchFiles(request.params.id, query.q, { + limit: query.limit, + type: query.type, + refresh: query.refresh, + }) + } catch (error) { + return handleWorkspaceError(error, reply) + } + }) + + app.get<{ + Params: { id: string } + Querystring: { path?: string } + }>("/api/workspaces/:id/files/content", async (request, reply) => { + try { + const query = WorkspaceFileContentQuerySchema.parse(request.query ?? {}) + return deps.workspaceManager.readFile(request.params.id, query.path) + } catch (error) { + return handleWorkspaceError(error, reply) + } + }) + + app.get<{ Params: { id: string } }>("/api/workspaces/:id/git/status", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + const result = spawnSync("git", ["-C", workspace.path, "status", "--porcelain=v1", "-b"], { encoding: "utf8" }) + if (result.error) { + return { + isRepo: false, + branch: null, + ahead: 0, + behind: 0, + changes: [], + error: result.error.message, + } + } + + if (result.status !== 0) { + const stderr = (result.stderr || "").toLowerCase() + if (stderr.includes("not a git repository")) { + return { isRepo: false, branch: null, ahead: 0, behind: 0, changes: [] } + } + reply.code(400) + return { + isRepo: false, + branch: null, + ahead: 0, + behind: 0, + changes: [], + error: result.stderr || "Unable to read git status", + } + } + + const lines = (result.stdout || "").split(/\r?\n/).filter((line) => line.trim().length > 0) + let branch: string | null = null + let ahead = 0 + let behind = 0 + const changes: Array<{ path: string; status: string }> = [] + + for (const line of lines) { + if (line.startsWith("##")) { + const header = line.replace(/^##\s*/, "") + const [branchPart, trackingPart] = header.split("...") + branch = branchPart?.trim() || null + const tracking = trackingPart || "" + const aheadMatch = tracking.match(/ahead\s+(\d+)/) + const behindMatch = tracking.match(/behind\s+(\d+)/) + ahead = aheadMatch ? Number(aheadMatch[1]) : 0 + behind = behindMatch ? Number(behindMatch[1]) : 0 + continue + } + + const status = line.slice(0, 2).trim() || line.slice(0, 2) + const path = line.slice(3).trim() + changes.push({ path, status }) + } + + return { isRepo: true, branch, ahead, behind, changes } + }) + + app.post<{ + Params: { id: string } + Body: { destination: string; includeConfig?: boolean } + }>("/api/workspaces/:id/export", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + const payload = request.body ?? { destination: "" } + const destination = payload.destination?.trim() + if (!destination) { + reply.code(400) + return { error: "Destination is required" } + } + + const exportRoot = path.join(destination, `nomadarch-export-${path.basename(workspace.path)}-${Date.now()}`) + mkdirSync(exportRoot, { recursive: true }) + + const workspaceTarget = path.join(exportRoot, "workspace") + await cp(workspace.path, workspaceTarget, { recursive: true, force: true }) + + const instanceData = await deps.instanceStore.read(workspace.path) + await writeFile(path.join(exportRoot, "instance-data.json"), JSON.stringify(instanceData, null, 2), "utf-8") + + const configDir = getWorkspaceOpencodeConfigDir(workspace.id) + if (existsSync(configDir)) { + await cp(configDir, path.join(exportRoot, "opencode-config"), { recursive: true, force: true }) + } + + if (payload.includeConfig) { + const config = deps.configStore.get() + await writeFile(path.join(exportRoot, "user-config.json"), JSON.stringify(config, null, 2), "utf-8") + } + + const metadata = { + exportedAt: new Date().toISOString(), + workspacePath: workspace.path, + workspaceId: workspace.id, + } + await writeFile(path.join(exportRoot, "metadata.json"), JSON.stringify(metadata, null, 2), "utf-8") + + return { destination: exportRoot } + }) + + app.get<{ Params: { id: string } }>("/api/workspaces/:id/mcp-config", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + const configPath = path.join(workspace.path, ".mcp.json") + if (!existsSync(configPath)) { + return { path: configPath, exists: false, config: { mcpServers: {} } } + } + + try { + const raw = await readFile(configPath, "utf-8") + const parsed = raw ? JSON.parse(raw) : {} + return { path: configPath, exists: true, config: parsed } + } catch (error) { + request.log.error({ err: error }, "Failed to read MCP config") + reply.code(500) + return { error: "Failed to read MCP config" } + } + }) + + app.put<{ Params: { id: string } }>("/api/workspaces/:id/mcp-config", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + const body = request.body as { config?: unknown } + if (!body || typeof body.config !== "object" || body.config === null) { + reply.code(400) + return { error: "Invalid MCP config payload" } + } + + const configPath = path.join(workspace.path, ".mcp.json") + try { + await writeFile(configPath, JSON.stringify(body.config, null, 2), "utf-8") + + // Auto-load MCP config into the manager after saving + const { getMcpManager } = await import("../../mcp/client") + const mcpManager = getMcpManager() + await mcpManager.loadConfig(workspace.path) + + return { path: configPath, exists: true, config: body.config } + } catch (error) { + request.log.error({ err: error }, "Failed to write MCP config") + reply.code(500) + return { error: "Failed to write MCP config" } + } + }) + + // Get MCP connection status for a workspace + app.get<{ Params: { id: string } }>("/api/workspaces/:id/mcp-status", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + try { + const { getMcpManager } = await import("../../mcp/client") + const mcpManager = getMcpManager() + + // Load config if not already loaded + await mcpManager.loadConfig(workspace.path) + + const status = mcpManager.getStatus() + const tools = await mcpManager.getAllTools() + + return { + servers: status, + toolCount: tools.length, + tools: tools.map(t => ({ name: t.name, server: t.serverName, description: t.description })) + } + } catch (error) { + request.log.error({ err: error }, "Failed to get MCP status") + reply.code(500) + return { error: "Failed to get MCP status" } + } + }) + + // Connect all configured MCPs for a workspace + app.post<{ Params: { id: string } }>("/api/workspaces/:id/mcp-connect", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + try { + const { getMcpManager } = await import("../../mcp/client") + const mcpManager = getMcpManager() + + // Load config first + await mcpManager.loadConfig(workspace.path) + + // Explicitly connect all servers + const connectionResults = await mcpManager.connectAll() + + // Get tools from connected servers + const tools = await mcpManager.getAllTools() + + // Transform connection results to status format + const status: Record = {} + for (const [name, result] of Object.entries(connectionResults)) { + status[name] = { connected: result.connected } + } + + return { + success: true, + servers: status, + toolCount: tools.length, + connectionDetails: connectionResults + } + } catch (error) { + request.log.error({ err: error }, "Failed to connect MCPs") + reply.code(500) + return { error: "Failed to connect MCPs" } + } + }) + + app.post<{ + Params: { id: string } + Body: { name: string; description?: string; systemPrompt: string; mode?: string } + }>("/api/workspaces/:id/agents", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + const { name, description, systemPrompt } = request.body + if (!name || !systemPrompt) { + reply.code(400) + return { error: "Name and systemPrompt are required" } + } + + try { + const data = await deps.instanceStore.read(workspace.path) + const customAgents = data.customAgents || [] + + // Update existing or add new + const existingIndex = customAgents.findIndex(a => a.name === name) + const agentData = { name, description, prompt: systemPrompt } + + if (existingIndex >= 0) { + customAgents[existingIndex] = agentData + } else { + customAgents.push(agentData) + } + + await deps.instanceStore.write(workspace.path, { + ...data, + customAgents + }) + + return { success: true, agent: agentData } + } catch (error) { + request.log.error({ err: error }, "Failed to save custom agent") + reply.code(500) + return { error: "Failed to save custom agent" } + } + }) + + app.post<{ + Body: { source: string; destination: string; includeConfig?: boolean } + }>("/api/workspaces/import", async (request, reply) => { + const payload = request.body ?? { source: "", destination: "" } + const source = payload.source?.trim() + const destination = payload.destination?.trim() + if (!source || !destination) { + reply.code(400) + return { error: "Source and destination are required" } + } + + const workspaceSource = path.join(source, "workspace") + if (!existsSync(workspaceSource)) { + reply.code(400) + return { error: "Export workspace folder not found" } + } + + await cp(workspaceSource, destination, { recursive: true, force: true }) + + const workspace = await deps.workspaceManager.create(destination) + + const instanceDataPath = path.join(source, "instance-data.json") + if (existsSync(instanceDataPath)) { + const raw = await readFile(instanceDataPath, "utf-8") + await deps.instanceStore.write(workspace.path, JSON.parse(raw)) + } + + const configSource = path.join(source, "opencode-config") + if (existsSync(configSource)) { + const configTarget = getWorkspaceOpencodeConfigDir(workspace.id) + await cp(configSource, configTarget, { recursive: true, force: true }) + } + + if (payload.includeConfig) { + const userConfigPath = path.join(source, "user-config.json") + if (existsSync(userConfigPath)) { + const raw = await readFile(userConfigPath, "utf-8") + deps.configStore.replace(JSON.parse(raw)) + } + } + + return workspace + }) + + // Serve static files from workspace for preview + app.get<{ Params: { id: string; "*": string } }>("/api/workspaces/:id/serve/*", async (request, reply) => { + const workspace = deps.workspaceManager.get(request.params.id) + if (!workspace) { + reply.code(404) + return { error: "Workspace not found" } + } + + const relativePath = request.params["*"] + const filePath = path.join(workspace.path, relativePath) + + // Security check: ensure file is within workspace.path + if (!filePath.startsWith(workspace.path)) { + reply.code(403) + return { error: "Access denied" } + } + + if (!existsSync(filePath)) { + reply.code(404) + return { error: "File not found" } + } + + const stat = await readFileStat(filePath) + if (!stat.isFile()) { + reply.code(400) + return { error: "Not a file" } + } + + const ext = path.extname(filePath).toLowerCase() + const mimeTypes: Record = { + ".html": "text/html", + ".htm": "text/html", + ".js": "application/javascript", + ".css": "text/css", + ".json": "application/json", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".svg": "image/svg+xml", + ".txt": "text/plain", + } + + reply.type(mimeTypes[ext] || "application/octet-stream") + return await readFile(filePath) + }) +} + + +function handleWorkspaceError(error: unknown, reply: FastifyReply) { + if (error instanceof Error && error.message === "Workspace not found") { + reply.code(404) + return { error: "Workspace not found" } + } + reply.code(400) + return { error: error instanceof Error ? error.message : "Unable to fulfill request" } +} diff --git a/packages/server/src/server/routes/zai.ts b/packages/server/src/server/routes/zai.ts new file mode 100644 index 0000000..8172569 --- /dev/null +++ b/packages/server/src/server/routes/zai.ts @@ -0,0 +1,367 @@ +import { FastifyInstance } from "fastify" +import { ZAIClient, ZAI_MODELS, type ZAIConfig, type ZAIChatRequest, type ZAIMessage } from "../../integrations/zai-api" +import { Logger } from "../../logger" +import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs" +import { join } from "path" +import { getUserIntegrationsDir } from "../../user-data" +import { CORE_TOOLS, executeTools, type ToolCall, type ToolResult } from "../../tools/executor" +import { getMcpManager } from "../../mcp/client" + +interface ZAIRouteDeps { + logger: Logger +} + +const CONFIG_DIR = getUserIntegrationsDir() +const CONFIG_FILE = join(CONFIG_DIR, "zai-config.json") + +// Maximum number of tool execution loops to prevent infinite recursion +const MAX_TOOL_LOOPS = 10 + +export async function registerZAIRoutes( + app: FastifyInstance, + deps: ZAIRouteDeps +) { + const logger = deps.logger.child({ component: "zai-routes" }) + + // Ensure config directory exists + if (!existsSync(CONFIG_DIR)) { + mkdirSync(CONFIG_DIR, { recursive: true }) + } + + // Get Z.AI configuration + app.get('/api/zai/config', async (request, reply) => { + try { + const config = getZAIConfig() + return { config: { ...config, apiKey: config.apiKey ? '***' : undefined } } + } catch (error) { + logger.error({ error }, "Failed to get Z.AI config") + return reply.status(500).send({ error: "Failed to get Z.AI configuration" }) + } + }) + + // Update Z.AI configuration + app.post('/api/zai/config', async (request, reply) => { + try { + const { enabled, apiKey, endpoint } = request.body as Partial + updateZAIConfig({ enabled, apiKey, endpoint }) + logger.info("Z.AI configuration updated") + return { success: true, config: { enabled, endpoint, apiKey: apiKey ? '***' : undefined } } + } catch (error) { + logger.error({ error }, "Failed to update Z.AI config") + return reply.status(500).send({ error: "Failed to update Z.AI configuration" }) + } + }) + + // Test Z.AI connection + app.post('/api/zai/test', async (request, reply) => { + try { + const config = getZAIConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Z.AI is not enabled" }) + } + + const client = new ZAIClient(config) + const isConnected = await client.testConnection() + + return { connected: isConnected } + } catch (error) { + logger.error({ error }, "Z.AI connection test failed") + return reply.status(500).send({ error: "Connection test failed" }) + } + }) + + // List available models + app.get('/api/zai/models', async (request, reply) => { + try { + return { models: ZAI_MODELS.map(name => ({ name, provider: "zai" })) } + } catch (error) { + logger.error({ error }, "Failed to list Z.AI models") + return reply.status(500).send({ error: "Failed to list models" }) + } + }) + + // Chat completion endpoint WITH MCP TOOL SUPPORT + app.post('/api/zai/chat', async (request, reply) => { + try { + const config = getZAIConfig() + if (!config.enabled) { + return reply.status(400).send({ error: "Z.AI is not enabled" }) + } + + const client = new ZAIClient(config) + const chatRequest = request.body as ZAIChatRequest & { + workspacePath?: string + enableTools?: boolean + } + + // Extract workspace path for tool execution + // IMPORTANT: workspacePath must be provided by frontend, otherwise tools write to server directory + const workspacePath = chatRequest.workspacePath || process.cwd() + const enableTools = chatRequest.enableTools !== false // Default to true + + logger.info({ + workspacePath, + receivedWorkspacePath: chatRequest.workspacePath, + enableTools + }, "Z.AI chat request received") + + // Load MCP tools from workspace config + let allTools = [...CORE_TOOLS] + if (enableTools && workspacePath) { + try { + const mcpManager = getMcpManager() + await mcpManager.loadConfig(workspacePath) + const mcpTools = await mcpManager.getToolsAsOpenAIFormat() + allTools = [...CORE_TOOLS, ...mcpTools] + if (mcpTools.length > 0) { + logger.info({ mcpToolCount: mcpTools.length }, "Loaded MCP tools") + } + } catch (mcpError) { + logger.warn({ error: mcpError }, "Failed to load MCP tools, using core tools only") + } + } + + // Inject tools into request if enabled + const requestWithTools: ZAIChatRequest = { + ...chatRequest, + tools: enableTools ? allTools : undefined, + tool_choice: enableTools ? "auto" : undefined + } + + // Handle streaming with tool execution loop + if (chatRequest.stream) { + reply.raw.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + 'Connection': 'keep-alive', + }) + + try { + await streamWithToolLoop( + client, + requestWithTools, + workspacePath, + enableTools, + reply.raw, + logger + ) + reply.raw.end() + } catch (streamError) { + logger.error({ error: streamError }, "Z.AI streaming failed") + reply.raw.write(`data: ${JSON.stringify({ error: String(streamError) })}\n\n`) + reply.raw.end() + } + } else { + // Non-streaming with tool loop + const response = await chatWithToolLoop( + client, + requestWithTools, + workspacePath, + enableTools, + logger + ) + return response + } + } catch (error) { + logger.error({ error }, "Z.AI chat request failed") + return reply.status(500).send({ error: "Chat request failed" }) + } + }) + + logger.info("Z.AI routes registered with MCP tool support") +} + +/** + * Streaming chat with tool execution loop + */ +async function streamWithToolLoop( + client: ZAIClient, + request: ZAIChatRequest, + workspacePath: string, + enableTools: boolean, + rawResponse: any, + logger: Logger +): Promise { + let messages = [...request.messages] + let loopCount = 0 + + while (loopCount < MAX_TOOL_LOOPS) { + loopCount++ + + // Accumulate tool calls from stream + let accumulatedToolCalls: { [index: number]: { id: string; name: string; arguments: string } } = {} + let hasToolCalls = false + let textContent = "" + + // Stream response + for await (const chunk of client.chatStream({ ...request, messages })) { + // Write chunk to client + rawResponse.write(`data: ${JSON.stringify(chunk)}\n\n`) + + const choice = chunk.choices[0] + if (!choice) continue + + // Accumulate text content + if (choice.delta?.content) { + textContent += choice.delta.content + } + + // Accumulate tool calls from delta + if (choice.delta?.tool_calls) { + hasToolCalls = true + for (const tc of choice.delta.tool_calls) { + const idx = tc.index ?? 0 + if (!accumulatedToolCalls[idx]) { + accumulatedToolCalls[idx] = { id: tc.id || "", name: "", arguments: "" } + } + if (tc.id) accumulatedToolCalls[idx].id = tc.id + if (tc.function?.name) accumulatedToolCalls[idx].name += tc.function.name + if (tc.function?.arguments) accumulatedToolCalls[idx].arguments += tc.function.arguments + } + } + + // Check if we should stop + if (choice.finish_reason === "stop") { + rawResponse.write('data: [DONE]\n\n') + return + } + } + + // If no tool calls, we're done + if (!hasToolCalls || !enableTools) { + rawResponse.write('data: [DONE]\n\n') + return + } + + // Convert accumulated tool calls + const toolCalls: ToolCall[] = Object.values(accumulatedToolCalls).map(tc => ({ + id: tc.id, + type: "function" as const, + function: { + name: tc.name, + arguments: tc.arguments + } + })) + + if (toolCalls.length === 0) { + rawResponse.write('data: [DONE]\n\n') + return + } + + logger.info({ toolCalls: toolCalls.map(tc => tc.function.name) }, "Executing tool calls") + + // Add assistant message with tool calls + const assistantMessage: ZAIMessage = { + role: "assistant", + content: textContent || undefined, + tool_calls: toolCalls + } + messages.push(assistantMessage) + + // Execute tools + const toolResults = await executeTools(workspacePath, toolCalls) + + // Notify client about tool execution via special event + for (const result of toolResults) { + const toolEvent = { + type: "tool_result", + tool_call_id: result.tool_call_id, + content: result.content + } + rawResponse.write(`data: ${JSON.stringify(toolEvent)}\n\n`) + } + + // Add tool results to messages + for (const result of toolResults) { + const toolMessage: ZAIMessage = { + role: "tool", + content: result.content, + tool_call_id: result.tool_call_id + } + messages.push(toolMessage) + } + + logger.info({ loopCount, toolsExecuted: toolResults.length }, "Tool loop iteration complete") + } + + logger.warn({ loopCount }, "Max tool loops reached") + rawResponse.write('data: [DONE]\n\n') +} + +/** + * Non-streaming chat with tool execution loop + */ +async function chatWithToolLoop( + client: ZAIClient, + request: ZAIChatRequest, + workspacePath: string, + enableTools: boolean, + logger: Logger +): Promise { + let messages = [...request.messages] + let loopCount = 0 + let lastResponse: any = null + + while (loopCount < MAX_TOOL_LOOPS) { + loopCount++ + + const response = await client.chat({ ...request, messages, stream: false }) + lastResponse = response + + const choice = response.choices[0] + if (!choice) break + + const toolCalls = choice.message?.tool_calls + + // If no tool calls or finish_reason is "stop", return + if (!toolCalls || toolCalls.length === 0 || !enableTools) { + return response + } + + logger.info({ toolCalls: toolCalls.map((tc: any) => tc.function.name) }, "Executing tool calls") + + // Add assistant message + const assistantMessage: ZAIMessage = { + role: "assistant", + content: choice.message.content || undefined, + tool_calls: toolCalls + } + messages.push(assistantMessage) + + // Execute tools + const toolResults = await executeTools(workspacePath, toolCalls) + + // Add tool results + for (const result of toolResults) { + const toolMessage: ZAIMessage = { + role: "tool", + content: result.content, + tool_call_id: result.tool_call_id + } + messages.push(toolMessage) + } + + logger.info({ loopCount, toolsExecuted: toolResults.length }, "Tool loop iteration complete") + } + + logger.warn({ loopCount }, "Max tool loops reached") + return lastResponse +} + +function getZAIConfig(): ZAIConfig { + try { + if (existsSync(CONFIG_FILE)) { + const data = readFileSync(CONFIG_FILE, 'utf-8') + return JSON.parse(data) + } + return { enabled: false, endpoint: "https://api.z.ai/api/coding/paas/v4", timeout: 300000 } + } catch { + return { enabled: false, endpoint: "https://api.z.ai/api/coding/paas/v4", timeout: 300000 } + } +} + +function updateZAIConfig(config: Partial): void { + const current = getZAIConfig() + const updated = { ...current, ...config } + writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2)) +} diff --git a/packages/server/src/storage/instance-store.ts b/packages/server/src/storage/instance-store.ts new file mode 100644 index 0000000..6361a7d --- /dev/null +++ b/packages/server/src/storage/instance-store.ts @@ -0,0 +1,65 @@ +import fs from "fs" +import { promises as fsp } from "fs" +import path from "path" +import type { InstanceData } from "../api-types" +import { getUserInstancesDir } from "../user-data" + +const DEFAULT_INSTANCE_DATA: InstanceData = { + messageHistory: [], + agentModelSelections: {}, + sessionTasks: {}, +} + +export class InstanceStore { + private readonly instancesDir: string + + constructor(baseDir = getUserInstancesDir()) { + this.instancesDir = baseDir + fs.mkdirSync(this.instancesDir, { recursive: true }) + } + + async read(id: string): Promise { + try { + const filePath = this.resolvePath(id) + const content = await fsp.readFile(filePath, "utf-8") + const parsed = JSON.parse(content) + return { ...DEFAULT_INSTANCE_DATA, ...parsed } + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return DEFAULT_INSTANCE_DATA + } + throw error + } + } + + async write(id: string, data: InstanceData): Promise { + const filePath = this.resolvePath(id) + await fsp.mkdir(path.dirname(filePath), { recursive: true }) + await fsp.writeFile(filePath, JSON.stringify(data, null, 2), "utf-8") + } + + async delete(id: string): Promise { + try { + const filePath = this.resolvePath(id) + await fsp.unlink(filePath) + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") { + throw error + } + } + } + + private resolvePath(id: string): string { + const filename = this.sanitizeId(id) + return path.join(this.instancesDir, `${filename}.json`) + } + + private sanitizeId(id: string): string { + return id + .replace(/[\\/]/g, "_") + .replace(/[^a-zA-Z0-9_.-]/g, "_") + .replace(/_{2,}/g, "_") + .replace(/^_|_$/g, "") + .toLowerCase() + } +} diff --git a/packages/server/src/storage/session-store.ts b/packages/server/src/storage/session-store.ts new file mode 100644 index 0000000..3666bbd --- /dev/null +++ b/packages/server/src/storage/session-store.ts @@ -0,0 +1,284 @@ +/** + * Session Store - Native session management without OpenCode binary + * + * This provides a complete replacement for OpenCode's session management, + * allowing NomadArch to work in "Binary-Free Mode". + */ + +import { readFile, writeFile, mkdir } from "fs/promises" +import { existsSync } from "fs" +import path from "path" +import { ulid } from "ulid" +import { createLogger } from "../logger" + +const log = createLogger({ component: "session-store" }) + +// Types matching OpenCode's schema for compatibility +export interface SessionMessage { + id: string + sessionId: string + role: "user" | "assistant" | "system" | "tool" + content?: string + parts?: MessagePart[] + createdAt: number + updatedAt: number + toolCalls?: ToolCall[] + toolCallId?: string + status?: "pending" | "streaming" | "completed" | "error" +} + +export interface MessagePart { + type: "text" | "tool_call" | "tool_result" | "thinking" | "code" + content?: string + toolCall?: ToolCall + toolResult?: ToolResult +} + +export interface ToolCall { + id: string + type: "function" + function: { + name: string + arguments: string + } +} + +export interface ToolResult { + toolCallId: string + content: string + isError?: boolean +} + +export interface Session { + id: string + workspaceId: string + title?: string + parentId?: string | null + createdAt: number + updatedAt: number + messageIds: string[] + model?: { + providerId: string + modelId: string + } + agent?: string + revert?: { + messageID: string + reason?: string + } | null +} + +export interface SessionStore { + sessions: Record + messages: Record +} + +/** + * Native session management for Binary-Free Mode + */ +export class NativeSessionManager { + private stores = new Map() + private dataDir: string + + constructor(dataDir: string) { + this.dataDir = dataDir + } + + private getStorePath(workspaceId: string): string { + return path.join(this.dataDir, workspaceId, "sessions.json") + } + + private async ensureDir(workspaceId: string): Promise { + const dir = path.join(this.dataDir, workspaceId) + if (!existsSync(dir)) { + await mkdir(dir, { recursive: true }) + } + } + + private async loadStore(workspaceId: string): Promise { + if (this.stores.has(workspaceId)) { + return this.stores.get(workspaceId)! + } + + const storePath = this.getStorePath(workspaceId) + let store: SessionStore = { sessions: {}, messages: {} } + + if (existsSync(storePath)) { + try { + const data = await readFile(storePath, "utf-8") + store = JSON.parse(data) + } catch (error) { + log.error({ workspaceId, error }, "Failed to load session store") + } + } + + this.stores.set(workspaceId, store) + return store + } + + private async saveStore(workspaceId: string): Promise { + const store = this.stores.get(workspaceId) + if (!store) return + + await this.ensureDir(workspaceId) + const storePath = this.getStorePath(workspaceId) + await writeFile(storePath, JSON.stringify(store, null, 2), "utf-8") + } + + // Session CRUD operations + + async listSessions(workspaceId: string): Promise { + const store = await this.loadStore(workspaceId) + return Object.values(store.sessions).sort((a, b) => b.updatedAt - a.updatedAt) + } + + async getSession(workspaceId: string, sessionId: string): Promise { + const store = await this.loadStore(workspaceId) + return store.sessions[sessionId] ?? null + } + + async createSession(workspaceId: string, options?: { + title?: string + parentId?: string + model?: { providerId: string; modelId: string } + agent?: string + }): Promise { + const store = await this.loadStore(workspaceId) + const now = Date.now() + + const session: Session = { + id: ulid(), + workspaceId, + title: options?.title ?? "New Session", + parentId: options?.parentId ?? null, + createdAt: now, + updatedAt: now, + messageIds: [], + model: options?.model, + agent: options?.agent, + } + + store.sessions[session.id] = session + await this.saveStore(workspaceId) + + log.info({ workspaceId, sessionId: session.id }, "Created new session") + return session + } + + async updateSession(workspaceId: string, sessionId: string, updates: Partial): Promise { + const store = await this.loadStore(workspaceId) + const session = store.sessions[sessionId] + if (!session) return null + + const updated = { + ...session, + ...updates, + id: session.id, // Prevent ID change + workspaceId: session.workspaceId, // Prevent workspace change + updatedAt: Date.now(), + } + + store.sessions[sessionId] = updated + await this.saveStore(workspaceId) + return updated + } + + async deleteSession(workspaceId: string, sessionId: string): Promise { + const store = await this.loadStore(workspaceId) + const session = store.sessions[sessionId] + if (!session) return false + + // Delete all messages in the session + for (const messageId of session.messageIds) { + delete store.messages[messageId] + } + + delete store.sessions[sessionId] + await this.saveStore(workspaceId) + + log.info({ workspaceId, sessionId }, "Deleted session") + return true + } + + // Message operations + + async getSessionMessages(workspaceId: string, sessionId: string): Promise { + const store = await this.loadStore(workspaceId) + const session = store.sessions[sessionId] + if (!session) return [] + + return session.messageIds + .map(id => store.messages[id]) + .filter((msg): msg is SessionMessage => msg !== undefined) + } + + async addMessage(workspaceId: string, sessionId: string, message: Omit): Promise { + const store = await this.loadStore(workspaceId) + const session = store.sessions[sessionId] + if (!session) throw new Error(`Session not found: ${sessionId}`) + + const now = Date.now() + const newMessage: SessionMessage = { + ...message, + id: ulid(), + sessionId, + createdAt: now, + updatedAt: now, + } + + store.messages[newMessage.id] = newMessage + session.messageIds.push(newMessage.id) + session.updatedAt = now + + await this.saveStore(workspaceId) + return newMessage + } + + async updateMessage(workspaceId: string, messageId: string, updates: Partial): Promise { + const store = await this.loadStore(workspaceId) + const message = store.messages[messageId] + if (!message) return null + + const updated = { + ...message, + ...updates, + id: message.id, // Prevent ID change + sessionId: message.sessionId, // Prevent session change + updatedAt: Date.now(), + } + + store.messages[messageId] = updated + await this.saveStore(workspaceId) + return updated + } + + // Utility + + async clearWorkspace(workspaceId: string): Promise { + this.stores.delete(workspaceId) + // Optionally delete file + } + + getActiveSessionCount(workspaceId: string): number { + const store = this.stores.get(workspaceId) + return store ? Object.keys(store.sessions).length : 0 + } +} + +// Singleton instance +let sessionManager: NativeSessionManager | null = null + +export function getSessionManager(dataDir?: string): NativeSessionManager { + if (!sessionManager) { + if (!dataDir) { + throw new Error("Session manager not initialized - provide dataDir") + } + sessionManager = new NativeSessionManager(dataDir) + } + return sessionManager +} + +export function initSessionManager(dataDir: string): NativeSessionManager { + sessionManager = new NativeSessionManager(dataDir) + return sessionManager +} diff --git a/packages/server/src/tools/executor.ts b/packages/server/src/tools/executor.ts new file mode 100644 index 0000000..be4a920 --- /dev/null +++ b/packages/server/src/tools/executor.ts @@ -0,0 +1,352 @@ +/** + * Tool Executor Service + * Provides MCP-compatible tool definitions and execution for all AI models. + * This enables Z.AI, Qwen, OpenCode Zen, etc. to write files, read files, and interact with the workspace. + */ + +import fs from "fs" +import path from "path" +import { createLogger } from "../logger" +import { getMcpManager } from "../mcp/client" + +const log = createLogger({ component: "tool-executor" }) + +// OpenAI-compatible Tool Definition Schema +export interface ToolDefinition { + type: "function" + function: { + name: string + description: string + parameters: { + type: "object" + properties: Record + required?: string[] + } + } +} + +// Tool Call from LLM Response +export interface ToolCall { + id: string + type: "function" + function: { + name: string + arguments: string // JSON string + } +} + +// Tool Execution Result +export interface ToolResult { + tool_call_id: string + role: "tool" + content: string +} + +/** + * Core Tool Definitions for MCP + * These follow OpenAI's function calling schema (compatible with Z.AI GLM-4) + */ +export const CORE_TOOLS: ToolDefinition[] = [ + { + type: "function", + function: { + name: "write_file", + description: "Write content to a file in the workspace. Creates the file if it doesn't exist, or overwrites if it does. Use this to generate code files, configuration, or any text content.", + parameters: { + type: "object", + properties: { + path: { + type: "string", + description: "Relative path to the file within the workspace (e.g., 'src/components/Button.tsx')" + }, + content: { + type: "string", + description: "The full content to write to the file" + } + }, + required: ["path", "content"] + } + } + }, + { + type: "function", + function: { + name: "read_file", + description: "Read the contents of a file from the workspace.", + parameters: { + type: "object", + properties: { + path: { + type: "string", + description: "Relative path to the file within the workspace" + } + }, + required: ["path"] + } + } + }, + { + type: "function", + function: { + name: "list_files", + description: "List files and directories in a workspace directory.", + parameters: { + type: "object", + properties: { + path: { + type: "string", + description: "Relative path to the directory (use '.' for root)" + } + }, + required: ["path"] + } + } + }, + { + type: "function", + function: { + name: "create_directory", + description: "Create a directory in the workspace. Creates parent directories if needed.", + parameters: { + type: "object", + properties: { + path: { + type: "string", + description: "Relative path to the directory to create" + } + }, + required: ["path"] + } + } + }, + { + type: "function", + function: { + name: "delete_file", + description: "Delete a file from the workspace.", + parameters: { + type: "object", + properties: { + path: { + type: "string", + description: "Relative path to the file to delete" + } + }, + required: ["path"] + } + } + } +] + +/** + * Execute a tool call within a workspace context + */ +export async function executeTool( + workspacePath: string, + toolCall: ToolCall +): Promise { + const { id, function: fn } = toolCall + const name = fn.name + let args: Record + + try { + args = JSON.parse(fn.arguments) + } catch (e) { + return { + tool_call_id: id, + role: "tool", + content: `Error: Failed to parse tool arguments: ${fn.arguments}` + } + } + + log.info({ tool: name, args, workspacePath }, "Executing tool") + + try { + switch (name) { + case "write_file": { + const relativePath = String(args.path || "") + const content = String(args.content || "") + const fullPath = path.resolve(workspacePath, relativePath) + + // Security check: ensure we're still within workspace + if (!fullPath.startsWith(path.resolve(workspacePath))) { + return { + tool_call_id: id, + role: "tool", + content: `Error: Path escapes workspace boundary: ${relativePath}` + } + } + + // Ensure parent directory exists + const dir = path.dirname(fullPath) + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }) + } + + fs.writeFileSync(fullPath, content, "utf-8") + log.info({ path: relativePath, bytes: content.length }, "File written successfully") + return { + tool_call_id: id, + role: "tool", + content: `Successfully wrote ${content.length} bytes to ${relativePath}` + } + } + + case "read_file": { + const relativePath = String(args.path || "") + const fullPath = path.resolve(workspacePath, relativePath) + + if (!fullPath.startsWith(path.resolve(workspacePath))) { + return { + tool_call_id: id, + role: "tool", + content: `Error: Path escapes workspace boundary: ${relativePath}` + } + } + + if (!fs.existsSync(fullPath)) { + return { + tool_call_id: id, + role: "tool", + content: `Error: File not found: ${relativePath}` + } + } + + const content = fs.readFileSync(fullPath, "utf-8") + return { + tool_call_id: id, + role: "tool", + content: content.slice(0, 50000) // Limit to prevent context overflow + } + } + + case "list_files": { + const relativePath = String(args.path || ".") + const fullPath = path.resolve(workspacePath, relativePath) + + if (!fullPath.startsWith(path.resolve(workspacePath))) { + return { + tool_call_id: id, + role: "tool", + content: `Error: Path escapes workspace boundary: ${relativePath}` + } + } + + if (!fs.existsSync(fullPath)) { + return { + tool_call_id: id, + role: "tool", + content: `Error: Directory not found: ${relativePath}` + } + } + + const entries = fs.readdirSync(fullPath, { withFileTypes: true }) + const listing = entries.map(e => + e.isDirectory() ? `${e.name}/` : e.name + ).join("\n") + + return { + tool_call_id: id, + role: "tool", + content: listing || "(empty directory)" + } + } + + case "create_directory": { + const relativePath = String(args.path || "") + const fullPath = path.resolve(workspacePath, relativePath) + + if (!fullPath.startsWith(path.resolve(workspacePath))) { + return { + tool_call_id: id, + role: "tool", + content: `Error: Path escapes workspace boundary: ${relativePath}` + } + } + + fs.mkdirSync(fullPath, { recursive: true }) + return { + tool_call_id: id, + role: "tool", + content: `Successfully created directory: ${relativePath}` + } + } + + case "delete_file": { + const relativePath = String(args.path || "") + const fullPath = path.resolve(workspacePath, relativePath) + + if (!fullPath.startsWith(path.resolve(workspacePath))) { + return { + tool_call_id: id, + role: "tool", + content: `Error: Path escapes workspace boundary: ${relativePath}` + } + } + + if (!fs.existsSync(fullPath)) { + return { + tool_call_id: id, + role: "tool", + content: `Error: File not found: ${relativePath}` + } + } + + fs.unlinkSync(fullPath) + return { + tool_call_id: id, + role: "tool", + content: `Successfully deleted: ${relativePath}` + } + } + + default: { + // Check if this is an MCP tool (format: mcp_servername_toolname) + if (name.startsWith("mcp_")) { + try { + const mcpManager = getMcpManager() + const result = await mcpManager.executeTool(name, args) + return { + tool_call_id: id, + role: "tool", + content: result + } + } catch (mcpError) { + const message = mcpError instanceof Error ? mcpError.message : String(mcpError) + return { + tool_call_id: id, + role: "tool", + content: `MCP tool error: ${message}` + } + } + } + + return { + tool_call_id: id, + role: "tool", + content: `Error: Unknown tool: ${name}` + } + } + } + } catch (error) { + const message = error instanceof Error ? error.message : String(error) + log.error({ tool: name, error: message }, "Tool execution failed") + return { + tool_call_id: id, + role: "tool", + content: `Error executing ${name}: ${message}` + } + } +} + +/** + * Execute multiple tool calls in parallel + */ +export async function executeTools( + workspacePath: string, + toolCalls: ToolCall[] +): Promise { + return Promise.all( + toolCalls.map(tc => executeTool(workspacePath, tc)) + ) +} diff --git a/packages/server/src/tools/index.ts b/packages/server/src/tools/index.ts new file mode 100644 index 0000000..000b243 --- /dev/null +++ b/packages/server/src/tools/index.ts @@ -0,0 +1,13 @@ +/** + * Tools Module Index + * Exports MCP-compatible tool definitions and executor for AI agent integration. + */ + +export { + CORE_TOOLS, + executeTool, + executeTools, + type ToolDefinition, + type ToolCall, + type ToolResult +} from "./executor" diff --git a/packages/server/src/user-data.ts b/packages/server/src/user-data.ts new file mode 100644 index 0000000..a46d0ce --- /dev/null +++ b/packages/server/src/user-data.ts @@ -0,0 +1,28 @@ +import os from "os" +import path from "path" + +const DEFAULT_ROOT = path.join(os.homedir(), ".config", "codenomad") + +export function getUserDataRoot(): string { + const override = process.env.CODENOMAD_USER_DIR + if (override && override.trim().length > 0) { + return path.resolve(override) + } + return DEFAULT_ROOT +} + +export function getUserConfigPath(): string { + return path.join(getUserDataRoot(), "config.json") +} + +export function getUserInstancesDir(): string { + return path.join(getUserDataRoot(), "instances") +} + +export function getUserIntegrationsDir(): string { + return path.join(getUserDataRoot(), "integrations") +} + +export function getOpencodeWorkspacesRoot(): string { + return path.join(getUserDataRoot(), "opencode-workspaces") +} diff --git a/packages/server/src/utils/port.ts b/packages/server/src/utils/port.ts new file mode 100644 index 0000000..08b6b9a --- /dev/null +++ b/packages/server/src/utils/port.ts @@ -0,0 +1,35 @@ +import net from "net" + +const DEFAULT_START_PORT = 3000 +const MAX_PORT_ATTEMPTS = 50 + +function isPortAvailable(port: number): Promise { + return new Promise((resolve) => { + const server = net.createServer() + server.once("error", () => { + resolve(false) + }) + server.once("listening", () => { + server.close() + resolve(true) + }) + server.listen(port, "127.0.0.1") + }) +} + +export async function findAvailablePort(startPort: number = DEFAULT_START_PORT): Promise { + for (let port = startPort; port < startPort + MAX_PORT_ATTEMPTS; port++) { + if (await isPortAvailable(port)) { + return port + } + } + return 0 +} + +export async function getAvailablePort(preferredPort: number = DEFAULT_START_PORT): Promise { + const isAvailable = await isPortAvailable(preferredPort) + if (isAvailable) { + return preferredPort + } + return findAvailablePort(preferredPort + 1) +} diff --git a/packages/server/src/workspaces/instance-events.ts b/packages/server/src/workspaces/instance-events.ts new file mode 100644 index 0000000..5fff69a --- /dev/null +++ b/packages/server/src/workspaces/instance-events.ts @@ -0,0 +1,195 @@ +import { Agent, fetch } from "undici" +import { Agent as UndiciAgent } from "undici" +import { EventBus } from "../events/bus" +import { Logger } from "../logger" +import { WorkspaceManager } from "./manager" +import { InstanceStreamEvent, InstanceStreamStatus } from "../api-types" + +const INSTANCE_HOST = "127.0.0.1" +const STREAM_AGENT = new UndiciAgent({ bodyTimeout: 0, headersTimeout: 0 }) +const RECONNECT_DELAY_MS = 1000 + +interface InstanceEventBridgeOptions { + workspaceManager: WorkspaceManager + eventBus: EventBus + logger: Logger +} + +interface ActiveStream { + controller: AbortController + task: Promise +} + +export class InstanceEventBridge { + private readonly streams = new Map() + + constructor(private readonly options: InstanceEventBridgeOptions) { + const bus = this.options.eventBus + bus.on("workspace.started", (event) => this.startStream(event.workspace.id)) + bus.on("workspace.stopped", (event) => this.stopStream(event.workspaceId, "workspace stopped")) + bus.on("workspace.error", (event) => this.stopStream(event.workspace.id, "workspace error")) + } + + shutdown() { + for (const [id, active] of this.streams) { + active.controller.abort() + this.publishStatus(id, "disconnected") + } + this.streams.clear() + } + + private startStream(workspaceId: string) { + if (this.streams.has(workspaceId)) { + return + } + + const controller = new AbortController() + const task = this.runStream(workspaceId, controller.signal) + .catch((error) => { + if (!controller.signal.aborted) { + this.options.logger.warn({ workspaceId, err: error }, "Instance event stream failed") + this.publishStatus(workspaceId, "error", error instanceof Error ? error.message : String(error)) + } + }) + .finally(() => { + const active = this.streams.get(workspaceId) + if (active?.controller === controller) { + this.streams.delete(workspaceId) + } + }) + + this.streams.set(workspaceId, { controller, task }) + } + + private stopStream(workspaceId: string, reason?: string) { + const active = this.streams.get(workspaceId) + if (!active) { + return + } + active.controller.abort() + this.streams.delete(workspaceId) + this.publishStatus(workspaceId, "disconnected", reason) + } + + private async runStream(workspaceId: string, signal: AbortSignal) { + while (!signal.aborted) { + const port = this.options.workspaceManager.getInstancePort(workspaceId) + if (!port) { + await this.delay(RECONNECT_DELAY_MS, signal) + continue + } + + this.publishStatus(workspaceId, "connecting") + + try { + await this.consumeStream(workspaceId, port, signal) + } catch (error) { + if (signal.aborted) { + break + } + this.options.logger.warn({ workspaceId, err: error }, "Instance event stream disconnected") + this.publishStatus(workspaceId, "error", error instanceof Error ? error.message : String(error)) + await this.delay(RECONNECT_DELAY_MS, signal) + } + } + } + + private async consumeStream(workspaceId: string, port: number, signal: AbortSignal) { + const url = `http://${INSTANCE_HOST}:${port}/event` + const response = await fetch(url, { + headers: { Accept: "text/event-stream" }, + signal, + dispatcher: STREAM_AGENT, + }) + + if (!response.ok || !response.body) { + throw new Error(`Instance event stream unavailable (${response.status})`) + } + + this.publishStatus(workspaceId, "connected") + + const reader = response.body.getReader() + const decoder = new TextDecoder() + let buffer = "" + + while (!signal.aborted) { + const { done, value } = await reader.read() + if (done || !value) { + break + } + buffer += decoder.decode(value, { stream: true }) + buffer = this.flushEvents(buffer, workspaceId) + } + } + + private flushEvents(buffer: string, workspaceId: string) { + let separatorIndex = buffer.indexOf("\n\n") + + while (separatorIndex >= 0) { + const chunk = buffer.slice(0, separatorIndex) + buffer = buffer.slice(separatorIndex + 2) + this.processChunk(chunk, workspaceId) + separatorIndex = buffer.indexOf("\n\n") + } + + return buffer + } + + private processChunk(chunk: string, workspaceId: string) { + const lines = chunk.split(/\r?\n/) + const dataLines: string[] = [] + + for (const line of lines) { + if (line.startsWith(":")) { + continue + } + if (line.startsWith("data:")) { + dataLines.push(line.slice(5).trimStart()) + } + } + + if (dataLines.length === 0) { + return + } + + const payload = dataLines.join("\n").trim() + if (!payload) { + return + } + + try { + const event = JSON.parse(payload) as InstanceStreamEvent + this.options.logger.debug({ workspaceId, eventType: event.type }, "Instance SSE event received") + if (this.options.logger.isLevelEnabled("trace")) { + this.options.logger.trace({ workspaceId, event }, "Instance SSE event payload") + } + this.options.eventBus.publish({ type: "instance.event", instanceId: workspaceId, event }) + } catch (error) { + this.options.logger.warn({ workspaceId, chunk: payload, err: error }, "Failed to parse instance SSE payload") + } + } + + private publishStatus(instanceId: string, status: InstanceStreamStatus, reason?: string) { + this.options.logger.debug({ instanceId, status, reason }, "Instance SSE status updated") + this.options.eventBus.publish({ type: "instance.eventStatus", instanceId, status, reason }) + } + + private delay(duration: number, signal: AbortSignal) { + if (duration <= 0) { + return Promise.resolve() + } + return new Promise((resolve) => { + const timeout = setTimeout(() => { + signal.removeEventListener("abort", onAbort) + resolve() + }, duration) + + const onAbort = () => { + clearTimeout(timeout) + resolve() + } + + signal.addEventListener("abort", onAbort, { once: true }) + }) + } +} diff --git a/packages/server/src/workspaces/manager.ts b/packages/server/src/workspaces/manager.ts new file mode 100644 index 0000000..c746145 --- /dev/null +++ b/packages/server/src/workspaces/manager.ts @@ -0,0 +1,481 @@ +import path from "path" +import { spawnSync } from "child_process" +import { connect } from "net" +import { EventBus } from "../events/bus" +import { ConfigStore } from "../config/store" +import { BinaryRegistry } from "../config/binaries" +import { FileSystemBrowser } from "../filesystem/browser" +import { searchWorkspaceFiles, WorkspaceFileSearchOptions } from "../filesystem/search" +import { clearWorkspaceSearchCache } from "../filesystem/search-cache" +import { WorkspaceDescriptor, WorkspaceFileResponse, FileSystemEntry } from "../api-types" +import { WorkspaceRuntime, ProcessExitInfo } from "./runtime" +import { Logger } from "../logger" +import { ensureWorkspaceOpencodeConfig } from "../opencode-config" +import { getContextEngineService } from "../context-engine" + +const STARTUP_STABILITY_DELAY_MS = 1500 + +interface WorkspaceManagerOptions { + rootDir: string + configStore: ConfigStore + binaryRegistry: BinaryRegistry + eventBus: EventBus + logger: Logger +} + +interface WorkspaceRecord extends WorkspaceDescriptor { } + +export class WorkspaceManager { + private readonly workspaces = new Map() + private readonly runtime: WorkspaceRuntime + + constructor(private readonly options: WorkspaceManagerOptions) { + this.runtime = new WorkspaceRuntime(this.options.eventBus, this.options.logger) + } + + list(): WorkspaceDescriptor[] { + return Array.from(this.workspaces.values()) + } + + get(id: string): WorkspaceDescriptor | undefined { + return this.workspaces.get(id) + } + + getInstancePort(id: string): number | undefined { + return this.workspaces.get(id)?.port + } + + listFiles(workspaceId: string, relativePath = "."): FileSystemEntry[] { + const workspace = this.requireWorkspace(workspaceId) + const browser = new FileSystemBrowser({ rootDir: workspace.path }) + return browser.list(relativePath) + } + + searchFiles(workspaceId: string, query: string, options?: WorkspaceFileSearchOptions): FileSystemEntry[] { + const workspace = this.requireWorkspace(workspaceId) + return searchWorkspaceFiles(workspace.path, query, options) + } + + readFile(workspaceId: string, relativePath: string): WorkspaceFileResponse { + const workspace = this.requireWorkspace(workspaceId) + const browser = new FileSystemBrowser({ rootDir: workspace.path }) + const contents = browser.readFile(relativePath) + return { + workspaceId, + relativePath, + contents, + } + } + + async create(folder: string, name?: string): Promise { + + const id = `${Date.now().toString(36)}` + const binary = this.options.binaryRegistry.resolveDefault() + const resolvedBinaryPath = this.resolveBinaryPath(binary.path) + const workspacePath = path.isAbsolute(folder) ? folder : path.resolve(this.options.rootDir, folder) + clearWorkspaceSearchCache(workspacePath) + + this.options.logger.info({ workspaceId: id, folder: workspacePath, binary: resolvedBinaryPath }, "Creating workspace") + + const proxyPath = `/workspaces/${id}/instance` + + + const descriptor: WorkspaceRecord = { + id, + path: workspacePath, + name, + status: "starting", + proxyPath, + binaryId: resolvedBinaryPath, + binaryLabel: binary.label, + binaryVersion: binary.version, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + } + + if (!descriptor.binaryVersion) { + descriptor.binaryVersion = this.detectBinaryVersion(resolvedBinaryPath) + } + + this.workspaces.set(id, descriptor) + + + this.options.eventBus.publish({ type: "workspace.created", workspace: descriptor }) + + const preferences = this.options.configStore.get().preferences ?? {} + const userEnvironment = preferences.environmentVariables ?? {} + const opencodeConfigDir = ensureWorkspaceOpencodeConfig(id) + const environment = { + ...userEnvironment, + OPENCODE_CONFIG_DIR: opencodeConfigDir, + } + + try { + const { pid, port, exitPromise, getLastOutput } = await this.runtime.launch({ + workspaceId: id, + folder: workspacePath, + binaryPath: resolvedBinaryPath, + environment, + onExit: (info) => this.handleProcessExit(info.workspaceId, info), + }) + + await this.waitForWorkspaceReadiness({ workspaceId: id, port, exitPromise, getLastOutput }) + + descriptor.pid = pid + descriptor.port = port + descriptor.status = "ready" + descriptor.updatedAt = new Date().toISOString() + this.options.eventBus.publish({ type: "workspace.started", workspace: descriptor }) + this.options.logger.info({ workspaceId: id, port }, "Workspace ready") + + // Trigger Context-Engine indexing (non-blocking) + const contextEngine = getContextEngineService() + if (contextEngine) { + contextEngine.indexPath(workspacePath).catch((error) => { + this.options.logger.warn({ workspaceId: id, error }, "Context-Engine indexing failed") + }) + } + + return descriptor + } catch (error) { + descriptor.status = "error" + let errorMessage = error instanceof Error ? error.message : String(error) + + // Check for common OpenCode issues + if (errorMessage.includes('ENOENT') || errorMessage.includes('command not found')) { + errorMessage = `OpenCode binary not found at '${resolvedBinaryPath}'. Please install OpenCode CLI from https://opencode.ai/ and ensure it's in your PATH.` + } else if (errorMessage.includes('health check')) { + errorMessage = `Workspace health check failed. OpenCode started but is not responding correctly. Check OpenCode logs for details.` + } + + descriptor.error = errorMessage + descriptor.updatedAt = new Date().toISOString() + this.options.eventBus.publish({ type: "workspace.error", workspace: descriptor }) + this.options.logger.error({ workspaceId: id, err: error }, "Workspace failed to start") + throw new Error(errorMessage) + } + } + + async delete(id: string): Promise { + const workspace = this.workspaces.get(id) + if (!workspace) return undefined + + this.options.logger.info({ workspaceId: id }, "Stopping workspace") + const wasRunning = Boolean(workspace.pid) + if (wasRunning) { + await this.runtime.stop(id).catch((error) => { + this.options.logger.warn({ workspaceId: id, err: error }, "Failed to stop workspace process cleanly") + }) + } + + this.workspaces.delete(id) + clearWorkspaceSearchCache(workspace.path) + if (!wasRunning) { + this.options.eventBus.publish({ type: "workspace.stopped", workspaceId: id }) + } + return workspace + } + + async shutdown() { + this.options.logger.info("Shutting down all workspaces") + for (const [id, workspace] of this.workspaces) { + if (workspace.pid) { + this.options.logger.info({ workspaceId: id }, "Stopping workspace during shutdown") + await this.runtime.stop(id).catch((error) => { + this.options.logger.error({ workspaceId: id, err: error }, "Failed to stop workspace during shutdown") + }) + } else { + this.options.logger.debug({ workspaceId: id }, "Workspace already stopped") + } + } + this.workspaces.clear() + this.options.logger.info("All workspaces cleared") + } + + private requireWorkspace(id: string): WorkspaceRecord { + const workspace = this.workspaces.get(id) + if (!workspace) { + throw new Error("Workspace not found") + } + return workspace + } + + private resolveBinaryPath(identifier: string): string { + if (!identifier) { + return identifier + } + + const looksLikePath = identifier.includes("/") || identifier.includes("\\") || identifier.startsWith(".") + if (path.isAbsolute(identifier) || looksLikePath) { + return identifier + } + + const locator = process.platform === "win32" ? "where" : "which" + + try { + const result = spawnSync(locator, [identifier], { encoding: "utf8" }) + if (result.status === 0 && result.stdout) { + const resolved = result.stdout + .split(/\r?\n/) + .map((line) => line.trim()) + .find((line) => line.length > 0) + + if (resolved) { + this.options.logger.debug({ identifier, resolved }, "Resolved binary path from system PATH") + return resolved + } + } else if (result.error) { + this.options.logger.warn({ identifier, err: result.error }, "Failed to resolve binary path via locator command") + } + } catch (error) { + this.options.logger.warn({ identifier, err: error }, "Failed to resolve binary path from system PATH") + } + + return identifier + } + + private detectBinaryVersion(resolvedPath: string): string | undefined { + if (!resolvedPath) { + return undefined + } + + try { + const result = spawnSync(resolvedPath, ["--version"], { encoding: "utf8" }) + if (result.status === 0 && result.stdout) { + const line = result.stdout.split(/\r?\n/).find((entry) => entry.trim().length > 0) + if (line) { + const normalized = line.trim() + const versionMatch = normalized.match(/([0-9]+\.[0-9]+\.[0-9A-Za-z.-]+)/) + if (versionMatch) { + const version = versionMatch[1] + this.options.logger.debug({ binary: resolvedPath, version }, "Detected binary version") + return version + } + this.options.logger.debug({ binary: resolvedPath, reported: normalized }, "Binary reported version string") + return normalized + } + } else if (result.error) { + this.options.logger.warn({ binary: resolvedPath, err: result.error }, "Failed to read binary version") + } + } catch (error) { + this.options.logger.warn({ binary: resolvedPath, err: error }, "Failed to detect binary version") + } + + return undefined + } + + private async waitForWorkspaceReadiness(params: { + workspaceId: string + port: number + exitPromise: Promise + getLastOutput: () => string + }) { + + await Promise.race([ + this.waitForPortAvailability(params.port), + params.exitPromise.then((info) => { + throw this.buildStartupError( + params.workspaceId, + "exited before becoming ready", + info, + params.getLastOutput(), + ) + }), + ]) + + await this.waitForInstanceHealth(params) + + await Promise.race([ + this.delay(STARTUP_STABILITY_DELAY_MS), + params.exitPromise.then((info) => { + throw this.buildStartupError( + params.workspaceId, + "exited shortly after start", + info, + params.getLastOutput(), + ) + }), + ]) + } + + private async waitForInstanceHealth(params: { + workspaceId: string + port: number + exitPromise: Promise + getLastOutput: () => string + }) { + const probeResult = await Promise.race([ + this.probeInstance(params.workspaceId, params.port), + params.exitPromise.then((info) => { + throw this.buildStartupError( + params.workspaceId, + "exited during health checks", + info, + params.getLastOutput(), + ) + }), + ]) + + if (probeResult.ok) { + return + } + + const latestOutput = params.getLastOutput().trim() + const outputDetails = latestOutput ? ` Last output: ${latestOutput}` : "" + const reason = probeResult.reason ?? "Health check failed" + throw new Error(`Workspace ${params.workspaceId} failed health check: ${reason}.${outputDetails}`) + } + + private async probeInstance(workspaceId: string, port: number): Promise<{ ok: boolean; reason?: string }> { + // Try multiple possible health check endpoints + const endpoints = [ + `/project/current`, + `/health`, + `/status`, + `/`, + `/api/health` + ] + + this.options.logger.info({ workspaceId, port, endpoints }, "Starting health check probe") + + for (const endpoint of endpoints) { + const url = `http://127.0.0.1:${port}${endpoint}` + + try { + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 10000) // 10 second timeout + + const response = await fetch(url, { + method: 'GET', + headers: { + 'User-Agent': 'NomadArch-HealthCheck/1.0' + }, + signal: controller.signal + }) + + clearTimeout(timeoutId) + + this.options.logger.debug({ workspaceId, status: response.status, url, endpoint }, "Health probe response received") + + if (response.ok) { + this.options.logger.info({ workspaceId, port, endpoint }, "Health check passed") + return { ok: true } + } else { + this.options.logger.debug({ workspaceId, status: response.status, url, endpoint }, "Health probe endpoint returned error") + } + } catch (error) { + this.options.logger.debug({ workspaceId, port, err: error, url, endpoint }, "Health probe endpoint failed") + // Continue to next endpoint + } + } + + // All endpoints failed + const reason = `OpenCode server started but is not responding to any known health endpoints (/project/current, /health, /status, /, /api/health)` + this.options.logger.error({ workspaceId, port }, "All health check endpoints failed") + return { ok: false, reason } + } + + private buildStartupError( + workspaceId: string, + phase: string, + exitInfo: ProcessExitInfo, + lastOutput: string, + ): Error { + const exitDetails = this.describeExit(exitInfo) + const trimmedOutput = lastOutput.trim() + const outputDetails = trimmedOutput ? ` Last output: ${trimmedOutput}` : "" + return new Error(`Workspace ${workspaceId} ${phase} (${exitDetails}).${outputDetails}`) + } + + private waitForPortAvailability(port: number, timeoutMs = 5000): Promise { + this.options.logger.info({ port, timeoutMs }, "Waiting for port availability - STARTING") + + return new Promise((resolve, reject) => { + const deadline = Date.now() + timeoutMs + let settled = false + let retryTimer: NodeJS.Timeout | null = null + let attemptCount = 0 + + const cleanup = () => { + settled = true + if (retryTimer) { + clearTimeout(retryTimer) + retryTimer = null + } + } + + const tryConnect = () => { + if (settled) { + return + } + + attemptCount++ + this.options.logger.debug({ port, attempt: attemptCount, timeRemaining: Math.max(0, deadline - Date.now()) }, "Attempting to connect to workspace port") + + const socket = connect({ port, host: "127.0.0.1" }, () => { + this.options.logger.info({ port, attempt: attemptCount }, "Port is available - SUCCESS") + cleanup() + socket.end() + resolve() + }) + socket.once("error", (error) => { + this.options.logger.debug({ port, attempt: attemptCount, err: error instanceof Error ? error.message : String(error) }, "Port connection failed - retrying") + socket.destroy() + if (settled) { + return + } + if (Date.now() >= deadline) { + this.options.logger.error({ port, attempt: attemptCount, timeoutMs }, "Port did not become available - TIMEOUT") + cleanup() + reject(new Error(`Workspace port ${port} did not become ready within ${timeoutMs}ms`)) + } else { + retryTimer = setTimeout(() => { + retryTimer = null + tryConnect() + }, 100) + } + }) + } + + tryConnect() + }) + } + + private delay(durationMs: number): Promise { + if (durationMs <= 0) { + return Promise.resolve() + } + return new Promise((resolve) => setTimeout(resolve, durationMs)) + } + + private describeExit(info: ProcessExitInfo): string { + if (info.signal) { + return `signal ${info.signal}` + } + if (info.code !== null) { + return `code ${info.code}` + } + return "unknown reason" + } + + private handleProcessExit(workspaceId: string, info: { code: number | null; requested: boolean }) { + const workspace = this.workspaces.get(workspaceId) + if (!workspace) return + + this.options.logger.info({ workspaceId, ...info }, "Workspace process exited") + + workspace.pid = undefined + workspace.port = undefined + workspace.updatedAt = new Date().toISOString() + + if (info.requested || info.code === 0) { + workspace.status = "stopped" + workspace.error = undefined + this.options.eventBus.publish({ type: "workspace.stopped", workspaceId }) + } else { + workspace.status = "error" + workspace.error = `Process exited with code ${info.code}` + this.options.eventBus.publish({ type: "workspace.error", workspace }) + } + } +} diff --git a/packages/server/src/workspaces/runtime.ts b/packages/server/src/workspaces/runtime.ts new file mode 100644 index 0000000..e5a9734 --- /dev/null +++ b/packages/server/src/workspaces/runtime.ts @@ -0,0 +1,294 @@ +import { ChildProcess, spawn } from "child_process" +import { existsSync, statSync, accessSync, constants } from "fs" +import path from "path" +import { EventBus } from "../events/bus" +import { LogLevel, WorkspaceLogEntry } from "../api-types" +import { Logger } from "../logger" + +interface LaunchOptions { + workspaceId: string + folder: string + binaryPath: string + environment?: Record + onExit?: (info: ProcessExitInfo) => void +} + +export interface ProcessExitInfo { + workspaceId: string + code: number | null + signal: NodeJS.Signals | null + requested: boolean +} + +interface ManagedProcess { + child: ChildProcess + requestedStop: boolean +} + +export class WorkspaceRuntime { + private processes = new Map() + + constructor(private readonly eventBus: EventBus, private readonly logger: Logger) { } + + async launch(options: LaunchOptions): Promise<{ pid: number; port: number; exitPromise: Promise; getLastOutput: () => string }> { + this.validateFolder(options.folder) + + // Check if binary exists before attempting to launch + try { + accessSync(options.binaryPath, constants.F_OK) + } catch (error) { + throw new Error(`OpenCode binary not found: ${options.binaryPath}. Please install OpenCode CLI from https://opencode.ai/ and ensure it's in your PATH.`) + } + + const args = ["serve", "--port", "0", "--print-logs", "--log-level", "DEBUG"] + const env = { + ...process.env, + ...(options.environment ?? {}), + "OPENCODE_SERVER_HOST": "127.0.0.1", + "OPENCODE_SERVER_PORT": "0", + "NODE_ENV": "production" + } + + this.logger.info( + { workspaceId: options.workspaceId, binaryPath: options.binaryPath, args }, + "Starting OpenCode with arguments" + ) + + let exitResolve: ((info: ProcessExitInfo) => void) | null = null + const exitPromise = new Promise((resolveExit) => { + exitResolve = resolveExit + }) + + // Store recent output for debugging - keep last 20 lines from each stream + const MAX_OUTPUT_LINES = 20 + const recentStdout: string[] = [] + const recentStderr: string[] = [] + const getLastOutput = () => { + const combined: string[] = [] + if (recentStderr.length > 0) { + combined.push("=== STDERR ===") + combined.push(...recentStderr.slice(-10)) + } + if (recentStdout.length > 0) { + combined.push("=== STDOUT ===") + combined.push(...recentStdout.slice(-10)) + } + return combined.join("\n") + } + + return new Promise((resolve, reject) => { + this.logger.info( + { workspaceId: options.workspaceId, folder: options.folder, binary: options.binaryPath }, + "Launching OpenCode process", + ) + const child = spawn(options.binaryPath, args, { + cwd: options.folder, + env, + stdio: ["ignore", "pipe", "pipe"], + }) + + const managed: ManagedProcess = { child, requestedStop: false } + this.processes.set(options.workspaceId, managed) + + let stdoutBuffer = "" + let stderrBuffer = "" + let portFound = false + + let warningTimer: NodeJS.Timeout | null = null + + const startWarningTimer = () => { + warningTimer = setInterval(() => { + this.logger.warn({ workspaceId: options.workspaceId }, "Workspace runtime has not reported a port yet") + }, 10000) + } + + const stopWarningTimer = () => { + if (warningTimer) { + clearInterval(warningTimer) + warningTimer = null + } + } + + startWarningTimer() + + const cleanupStreams = () => { + stopWarningTimer() + child.stdout?.removeAllListeners() + child.stderr?.removeAllListeners() + } + + const handleExit = (code: number | null, signal: NodeJS.Signals | null) => { + this.logger.info({ workspaceId: options.workspaceId, code, signal }, "OpenCode process exited") + this.processes.delete(options.workspaceId) + cleanupStreams() + child.removeListener("error", handleError) + child.removeListener("exit", handleExit) + const exitInfo: ProcessExitInfo = { + workspaceId: options.workspaceId, + code, + signal, + requested: managed.requestedStop, + } + if (exitResolve) { + exitResolve(exitInfo) + exitResolve = null + } + if (!portFound) { + const reason = stderrBuffer || `Process exited with code ${code}` + reject(new Error(reason)) + } else { + options.onExit?.(exitInfo) + } + } + + const handleError = (error: Error) => { + cleanupStreams() + child.removeListener("exit", handleExit) + this.processes.delete(options.workspaceId) + this.logger.error({ workspaceId: options.workspaceId, err: error }, "Workspace runtime error") + if (exitResolve) { + exitResolve({ workspaceId: options.workspaceId, code: null, signal: null, requested: managed.requestedStop }) + exitResolve = null + } + reject(error) + } + + child.on("error", handleError) + child.on("exit", handleExit) + + child.stdout?.on("data", (data: Buffer) => { + const text = data.toString() + stdoutBuffer += text + const lines = stdoutBuffer.split("\n") + stdoutBuffer = lines.pop() ?? "" + + for (const line of lines) { + const trimmed = line.trim() + if (!trimmed) continue + + // Store in recent buffer for debugging + recentStdout.push(trimmed) + if (recentStdout.length > MAX_OUTPUT_LINES) { + recentStdout.shift() + } + + this.emitLog(options.workspaceId, "info", line) + + if (!portFound) { + this.logger.debug({ workspaceId: options.workspaceId, line: trimmed }, "OpenCode output line") + // Try multiple patterns for port detection + const portMatch = line.match(/opencode server listening on http:\/\/.+:(\d+)/i) || + line.match(/server listening on http:\/\/.+:(\d+)/i) || + line.match(/listening on http:\/\/.+:(\d+)/i) || + line.match(/:(\d+)/i) + + if (portMatch) { + portFound = true + child.removeListener("error", handleError) + const port = parseInt(portMatch[1], 10) + this.logger.info({ workspaceId: options.workspaceId, port, matchedLine: trimmed }, "Workspace runtime allocated port - PORT DETECTED") + resolve({ pid: child.pid!, port, exitPromise, getLastOutput }) + } else { + this.logger.debug({ workspaceId: options.workspaceId, line: trimmed }, "Port detection - no match in this line") + } + } + } + }) + + child.stderr?.on("data", (data: Buffer) => { + const text = data.toString() + stderrBuffer += text + const lines = stderrBuffer.split("\n") + stderrBuffer = lines.pop() ?? "" + + for (const line of lines) { + const trimmed = line.trim() + if (!trimmed) continue + + // Store in recent buffer for debugging + recentStderr.push(trimmed) + if (recentStderr.length > MAX_OUTPUT_LINES) { + recentStderr.shift() + } + + this.emitLog(options.workspaceId, "error", line) + } + }) + }) + } + + async stop(workspaceId: string): Promise { + const managed = this.processes.get(workspaceId) + if (!managed) return + + managed.requestedStop = true + const child = managed.child + this.logger.info({ workspaceId }, "Stopping OpenCode process") + + await new Promise((resolve, reject) => { + const cleanup = () => { + child.removeListener("exit", onExit) + child.removeListener("error", onError) + } + + const onExit = () => { + cleanup() + resolve() + } + const onError = (error: Error) => { + cleanup() + reject(error) + } + + const resolveIfAlreadyExited = () => { + if (child.exitCode !== null || child.signalCode !== null) { + this.logger.debug({ workspaceId, exitCode: child.exitCode, signal: child.signalCode }, "Process already exited") + cleanup() + resolve() + return true + } + return false + } + + child.once("exit", onExit) + child.once("error", onError) + + if (resolveIfAlreadyExited()) { + return + } + + this.logger.debug({ workspaceId }, "Sending SIGTERM to workspace process") + child.kill("SIGTERM") + setTimeout(() => { + if (!child.killed) { + this.logger.warn({ workspaceId }, "Process did not stop after SIGTERM, force killing") + child.kill("SIGKILL") + } else { + this.logger.debug({ workspaceId }, "Workspace process stopped gracefully before SIGKILL timeout") + } + }, 2000) + }) + } + + private emitLog(workspaceId: string, level: LogLevel, message: string) { + const entry: WorkspaceLogEntry = { + workspaceId, + timestamp: new Date().toISOString(), + level, + message: message.trim(), + } + + this.eventBus.publish({ type: "workspace.log", entry }) + } + + private validateFolder(folder: string) { + const resolved = path.resolve(folder) + if (!existsSync(resolved)) { + throw new Error(`Folder does not exist: ${resolved}`) + } + const stats = statSync(resolved) + if (!stats.isDirectory()) { + throw new Error(`Path is not a directory: ${resolved}`) + } + } +} diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json new file mode 100644 index 0000000..5f9cd23 --- /dev/null +++ b/packages/server/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "rootDir": "src", + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/tauri-app/.gitignore b/packages/tauri-app/.gitignore new file mode 100644 index 0000000..2bdc757 --- /dev/null +++ b/packages/tauri-app/.gitignore @@ -0,0 +1,7 @@ +src-tauri/target +src-tauri/Cargo.lock +src-tauri/resources/ +target +node_modules +dist +.DS_Store diff --git a/packages/tauri-app/Cargo.lock b/packages/tauri-app/Cargo.lock new file mode 100644 index 0000000..abffbd4 --- /dev/null +++ b/packages/tauri-app/Cargo.lock @@ -0,0 +1,5589 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "enumflags2", + "futures-channel", + "futures-util", + "rand 0.9.2", + "raw-window-handle", + "serde", + "serde_repr", + "tokio", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.1.2", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix 1.1.2", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "async-signal" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix 1.1.2", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.2", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.3", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.10.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.8", +] + +[[package]] +name = "cc" +version = "1.2.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd405d82c84ff7f35739f175f67d8b9fb7687a0e84ccdc78bd3568839827cf07" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "codenomad-tauri" +version = "0.1.0" +dependencies = [ + "anyhow", + "dirs 5.0.1", + "libc", + "once_cell", + "parking_lot", + "regex", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-dialog", + "tauri-plugin-opener", + "thiserror 1.0.69", + "url", + "which", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.10.0", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.110", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.110", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.110", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.110", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys 0.4.1", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys 0.5.0", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.4.6", + "windows-sys 0.48.0", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.10.0", + "block2 0.6.2", + "libc", + "objc2 0.6.3", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "dlopen2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b54f373ccf864bf587a89e880fb7610f8d73f3045f13580948ccbcaff26febff" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "788160fb30de9cdd857af31c6a2675904b16ece8fc2737b2c7127ba368c9d0f4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "embed-resource" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.8", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "endi" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.10.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.12.1", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags 2.10.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "png", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.10.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.10.0", + "block2 0.6.2", + "libc", + "objc2 0.6.3", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.10.0", + "dispatch2", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.10.0", + "block2 0.5.1", + "libc", + "objc2 0.5.2", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.10.0", + "block2 0.6.2", + "libc", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-javascript-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" +dependencies = [ + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.10.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.10.0", + "block2 0.5.1", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.10.0", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.10.0", + "block2 0.6.2", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-javascript-core", + "objc2-security", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.12.1", + "quick-xml 0.38.4", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.7", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.37.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.17", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.110", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +dependencies = [ + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18051cdd562e792cad055119e0cdb2cfc137e44e3987532e0f9659a77931bb08" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics", + "foreign-types", + "js-sys", + "log", + "objc2 0.5.2", + "objc2-foundation 0.2.2", + "objc2-quartz-core 0.2.2", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.110" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" +dependencies = [ + "bitflags 2.10.0", + "block2 0.6.2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e492485dd390b35f7497401f67694f46161a2a00ffd800938d5dd3c898fb9d8" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs 6.0.0", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.17", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87d6f8cafe6a75514ce5333f115b7b1866e8e68d9672bf4ca89fc0f35697ea9d" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs 6.0.0", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.8", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7ef707148f0755110ca54377560ab891d722de4d53297595380a748026f139f" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.110", + "tauri-utils", + "thiserror 2.0.17", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71664fd715ee6e382c05345ad258d6d1d50f90cf1b58c0aa726638b33c2a075d" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.110", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076c78a474a7247c90cad0b6e87e593c4c620ed4efdb79cbe0214f0021f6c39d" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.8", + "walkdir", +] + +[[package]] +name = "tauri-plugin-dialog" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "313f8138692ddc4a2127c4c9607d616a46f5c042e77b3722450866da0aad2f19" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.17", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47df422695255ecbe7bac7012440eddaeefd026656171eac9559f5243d3230d9" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.17", + "toml 0.9.8", + "url", +] + +[[package]] +name = "tauri-plugin-opener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26b72571d25dee25667940027114e60f569fc3974f8cefbe50c2cbc5fd65e3b" +dependencies = [ + "dunce", + "glob", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "open", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "thiserror 2.0.17", + "url", + "windows", + "zbus", +] + +[[package]] +name = "tauri-runtime" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9368f09358496f2229313fccb37682ad116b7f46fa76981efe116994a0628926" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2 0.6.3", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "929f5df216f5c02a9e894554401bcdab6eec3e39ec6a4a7731c7067fc8688a93" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-foundation 0.3.2", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b8bbe426abdbf52d050e52ed693130dbd68375b9ad82a3fb17efb4c8d85673" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.17", + "toml 0.9.8", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +dependencies = [ + "dunce", + "embed-resource", + "toml 0.9.8", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap 2.12.1", + "serde_core", + "serde_spanned 1.0.3", + "toml_datetime 0.7.3", + "toml_parser", + "toml_writer", + "winnow 0.7.13", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.12.1", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.12.1", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap 2.12.1", + "toml_datetime 0.7.3", + "toml_parser", + "winnow 0.7.13", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow 0.7.13", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d5572781bee8e3f994d7467084e1b1fd7a93ce66bd480f8156ba89dee55a2b" +dependencies = [ + "crossbeam-channel", + "dirs 6.0.0", + "libappindicator", + "muda", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "once_cell", + "png", + "serde", + "thiserror 2.0.17", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "uds_windows" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89daebc3e6fd160ac4aa9fc8b3bf71e1f74fbf92367ae71fb83a037e8bf164b9" +dependencies = [ + "memoffset", + "tempfile", + "winapi", +] + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.110", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wayland-backend" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673a33c33048a5ade91a6b139580fa174e19fb0d23f396dca9fa15f2e1e49b35" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.2", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d" +dependencies = [ + "bitflags 2.10.0", + "rustix 1.1.2", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" +dependencies = [ + "bitflags 2.10.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cb1e9dc49da91950bdfd8b848c49330536d9d1fb03d4bfec8cae50caa50ae3" +dependencies = [ + "proc-macro2", + "quick-xml 0.37.5", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34949b42822155826b41db8e5d0c1be3a2bd296c747577a43a3e6daefc296142" +dependencies = [ + "dlib", + "log", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ba622a989277ef3886dd5afb3e280e3dd6d974b766118950a08f8f678ad6a4" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36695906a1b53a3bf5c4289621efedac12b73eeb0b89e7e1a89b517302d5d75c" +dependencies = [ + "thiserror 2.0.17", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.44", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wry" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728b7d4c8ec8d81cab295e0b5b8a4c263c0d41a785fb8f8c4df284e5411140a2" +dependencies = [ + "base64 0.22.1", + "block2 0.6.2", + "cookie", + "crossbeam-channel", + "dirs 6.0.0", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2 0.6.3", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.17", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", + "synstructure", +] + +[[package]] +name = "zbus" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b622b18155f7a93d1cd2dc8c01d2d6a44e08fb9ebb7b3f9e6ed101488bad6c91" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "nix", + "ordered-stream", + "serde", + "serde_repr", + "tokio", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow 0.7.13", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "5.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cdb94821ca8a87ca9c298b5d1cbd80e2a8b67115d99f6e4551ac49e42b6a314" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.110", + "zbus_names", + "zvariant", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be68e64bf6ce8db94f63e72f0c7eb9a60d733f7e0499e628dfab0f84d6bcb97" +dependencies = [ + "serde", + "static_assertions", + "winnow 0.7.13", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fa6694ed34d6e57407afbccdeecfa268c470a7d2a5b0cf49ce9fcc345afb90" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c640b22cd9817fae95be82f0d2f90b11f7605f6c319d16705c459b27ac2cbc26" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "zvariant" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2be61892e4f2b1772727be11630a62664a1826b62efa43a6fe7449521cb8744c" +dependencies = [ + "endi", + "enumflags2", + "serde", + "url", + "winnow 0.7.13", + "zvariant_derive", + "zvariant_utils", +] + +[[package]] +name = "zvariant_derive" +version = "5.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58575a1b2b20766513b1ec59d8e2e68db2745379f961f86650655e862d2006" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.110", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6949d142f89f6916deca2232cf26a8afacf2b9fdc35ce766105e104478be599" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 2.0.110", + "winnow 0.7.13", +] diff --git a/packages/tauri-app/Cargo.toml b/packages/tauri-app/Cargo.toml new file mode 100644 index 0000000..5322fc2 --- /dev/null +++ b/packages/tauri-app/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["src-tauri"] +resolver = "2" diff --git a/packages/tauri-app/package.json b/packages/tauri-app/package.json new file mode 100644 index 0000000..1d16d5e --- /dev/null +++ b/packages/tauri-app/package.json @@ -0,0 +1,17 @@ +{ + "name": "@codenomad/tauri-app", + "version": "0.4.0", + "private": true, + "scripts": { + "dev": "npx --yes @tauri-apps/cli@^2.9.4 dev", + "dev:ui": "npm run dev --workspace @codenomad/ui", + "dev:prep": "node ./scripts/dev-prep.js", + "dev:bootstrap": "npm run dev:prep && npm run dev:ui", + "prebuild": "node ./scripts/prebuild.js", + "bundle:server": "npm run prebuild", + "build": "npx --yes @tauri-apps/cli@^2.9.4 build" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.9.4" + } +} diff --git a/packages/tauri-app/scripts/dev-prep.js b/packages/tauri-app/scripts/dev-prep.js new file mode 100644 index 0000000..abbb4c6 --- /dev/null +++ b/packages/tauri-app/scripts/dev-prep.js @@ -0,0 +1,46 @@ +#!/usr/bin/env node + +const fs = require("fs") +const path = require("path") +const { execSync } = require("child_process") + +const root = path.resolve(__dirname, "..") +const workspaceRoot = path.resolve(root, "..", "..") +const uiRoot = path.resolve(root, "..", "ui") +const uiDist = path.resolve(uiRoot, "src", "renderer", "dist") +const uiLoadingDest = path.resolve(root, "src-tauri", "resources", "ui-loading") + +function ensureUiBuild() { + const loadingHtml = path.join(uiDist, "loading.html") + if (fs.existsSync(loadingHtml)) { + return + } + + console.log("[dev-prep] UI loader build missing; running workspace build…") + execSync("npm --workspace @codenomad/ui run build", { + cwd: workspaceRoot, + stdio: "inherit", + }) + + if (!fs.existsSync(loadingHtml)) { + throw new Error("[dev-prep] failed to produce loading.html after UI build") + } +} + +function copyUiLoadingAssets() { + const loadingSource = path.join(uiDist, "loading.html") + const assetsSource = path.join(uiDist, "assets") + + fs.rmSync(uiLoadingDest, { recursive: true, force: true }) + fs.mkdirSync(uiLoadingDest, { recursive: true }) + + fs.copyFileSync(loadingSource, path.join(uiLoadingDest, "loading.html")) + if (fs.existsSync(assetsSource)) { + fs.cpSync(assetsSource, path.join(uiLoadingDest, "assets"), { recursive: true }) + } + + console.log(`[dev-prep] copied loader bundle from ${uiDist}`) +} + +ensureUiBuild() +copyUiLoadingAssets() diff --git a/packages/tauri-app/scripts/prebuild.js b/packages/tauri-app/scripts/prebuild.js new file mode 100644 index 0000000..c396892 --- /dev/null +++ b/packages/tauri-app/scripts/prebuild.js @@ -0,0 +1,195 @@ +#!/usr/bin/env node +const fs = require("fs") +const path = require("path") +const { execSync } = require("child_process") + +const root = path.resolve(__dirname, "..") +const workspaceRoot = path.resolve(root, "..", "..") +const serverRoot = path.resolve(root, "..", "server") +const uiRoot = path.resolve(root, "..", "ui") +const uiDist = path.resolve(uiRoot, "src", "renderer", "dist") +const serverDest = path.resolve(root, "src-tauri", "resources", "server") +const uiLoadingDest = path.resolve(root, "src-tauri", "resources", "ui-loading") + +const sources = ["dist", "public", "node_modules", "package.json"] + +const serverInstallCommand = + "npm install --omit=dev --ignore-scripts --workspaces=false --package-lock=false --install-strategy=shallow --fund=false --audit=false" +const serverDevInstallCommand = + "npm install --workspace @neuralnomads/codenomad --include-workspace-root=false --install-strategy=nested --fund=false --audit=false" +const uiDevInstallCommand = + "npm install --workspace @codenomad/ui --include-workspace-root=false --install-strategy=nested --fund=false --audit=false" + +const envWithRootBin = { + ...process.env, + PATH: `${path.join(workspaceRoot, "node_modules/.bin")}:${process.env.PATH}`, +} + +const braceExpansionPath = path.join( + serverRoot, + "node_modules", + "@fastify", + "static", + "node_modules", + "brace-expansion", + "package.json", +) + +const viteBinPath = path.join(uiRoot, "node_modules", ".bin", "vite") + +function ensureServerBuild() { + const distPath = path.join(serverRoot, "dist") + const publicPath = path.join(serverRoot, "public") + if (fs.existsSync(distPath) && fs.existsSync(publicPath)) { + return + } + + console.log("[prebuild] server build missing; running workspace build...") + execSync("npm --workspace @neuralnomads/codenomad run build", { + cwd: workspaceRoot, + stdio: "inherit", + env: { + ...process.env, + PATH: `${path.join(workspaceRoot, "node_modules/.bin")}:${process.env.PATH}`, + }, + }) + + if (!fs.existsSync(distPath) || !fs.existsSync(publicPath)) { + throw new Error("[prebuild] server artifacts still missing after build") + } +} + +function ensureUiBuild() { + const loadingHtml = path.join(uiDist, "loading.html") + if (fs.existsSync(loadingHtml)) { + return + } + + console.log("[prebuild] ui build missing; running workspace build...") + execSync("npm --workspace @codenomad/ui run build", { + cwd: workspaceRoot, + stdio: "inherit", + }) + + if (!fs.existsSync(loadingHtml)) { + throw new Error("[prebuild] ui loading assets missing after build") + } +} + +function ensureServerDevDependencies() { + if (fs.existsSync(braceExpansionPath)) { + return + } + + console.log("[prebuild] ensuring server build dependencies (with dev)...") + execSync(serverDevInstallCommand, { + cwd: workspaceRoot, + stdio: "inherit", + env: envWithRootBin, + }) +} + +function ensureServerDependencies() { + if (fs.existsSync(braceExpansionPath)) { + return + } + + console.log("[prebuild] ensuring server production dependencies...") + execSync(serverInstallCommand, { + cwd: serverRoot, + stdio: "inherit", + }) +} + +function ensureUiDevDependencies() { + if (fs.existsSync(viteBinPath)) { + return + } + + console.log("[prebuild] ensuring ui build dependencies...") + execSync(uiDevInstallCommand, { + cwd: workspaceRoot, + stdio: "inherit", + env: envWithRootBin, + }) +} + +function ensureRollupPlatformBinary() { + const platformKey = `${process.platform}-${process.arch}` + const platformPackages = { + "linux-x64": "@rollup/rollup-linux-x64-gnu", + "linux-arm64": "@rollup/rollup-linux-arm64-gnu", + "darwin-arm64": "@rollup/rollup-darwin-arm64", + "darwin-x64": "@rollup/rollup-darwin-x64", + "win32-x64": "@rollup/rollup-win32-x64-msvc", + } + + const pkgName = platformPackages[platformKey] + if (!pkgName) { + return + } + + const platformPackagePath = path.join(workspaceRoot, "node_modules", "@rollup", pkgName.split("/").pop()) + if (fs.existsSync(platformPackagePath)) { + return + } + + let rollupVersion = "" + try { + rollupVersion = require(path.join(workspaceRoot, "node_modules", "rollup", "package.json")).version + } catch (error) { + // leave version empty; fallback install will use latest compatible + } + + const packageSpec = rollupVersion ? `${pkgName}@${rollupVersion}` : pkgName + + console.log("[prebuild] installing rollup platform binary (optional dep workaround)...") + execSync(`npm install ${packageSpec} --no-save --ignore-scripts --fund=false --audit=false`, { + cwd: workspaceRoot, + stdio: "inherit", + }) +} + +function copyServerArtifacts() { + fs.rmSync(serverDest, { recursive: true, force: true }) + fs.mkdirSync(serverDest, { recursive: true }) + + for (const name of sources) { + const from = path.join(serverRoot, name) + const to = path.join(serverDest, name) + if (!fs.existsSync(from)) { + console.warn(`[prebuild] skipped missing ${from}`) + continue + } + fs.cpSync(from, to, { recursive: true, dereference: true }) + console.log(`[prebuild] copied ${from} -> ${to}`) + } +} + +function copyUiLoadingAssets() { + const loadingSource = path.join(uiDist, "loading.html") + const assetsSource = path.join(uiDist, "assets") + + if (!fs.existsSync(loadingSource)) { + throw new Error("[prebuild] cannot find built loading.html") + } + + fs.rmSync(uiLoadingDest, { recursive: true, force: true }) + fs.mkdirSync(uiLoadingDest, { recursive: true }) + + fs.copyFileSync(loadingSource, path.join(uiLoadingDest, "loading.html")) + if (fs.existsSync(assetsSource)) { + fs.cpSync(assetsSource, path.join(uiLoadingDest, "assets"), { recursive: true }) + } + + console.log(`[prebuild] prepared UI loading assets from ${uiDist}`) +} + +ensureServerDevDependencies() +ensureUiDevDependencies() +ensureRollupPlatformBinary() +ensureServerDependencies() +ensureServerBuild() +ensureUiBuild() +copyServerArtifacts() +copyUiLoadingAssets() diff --git a/packages/tauri-app/src-tauri/Cargo.toml b/packages/tauri-app/src-tauri/Cargo.toml new file mode 100644 index 0000000..11e770a --- /dev/null +++ b/packages/tauri-app/src-tauri/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "codenomad-tauri" +version = "0.1.0" +edition = "2021" + +[build-dependencies] +tauri-build = { version = "2.5.2", features = [] } + +[dependencies] +tauri = { version = "2.5.2", features = [ "devtools"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +regex = "1" +once_cell = "1" +parking_lot = "0.12" +thiserror = "1" +anyhow = "1" +which = "4" +libc = "0.2" +tauri-plugin-dialog = "2" +dirs = "5" +tauri-plugin-opener = "2" +url = "2" diff --git a/packages/tauri-app/src-tauri/build.rs b/packages/tauri-app/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/packages/tauri-app/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/packages/tauri-app/src-tauri/capabilities/main-window.json b/packages/tauri-app/src-tauri/capabilities/main-window.json new file mode 100644 index 0000000..61d6e34 --- /dev/null +++ b/packages/tauri-app/src-tauri/capabilities/main-window.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://schema.tauri.app/capabilities.json", + "identifier": "main-window-native-dialogs", + "description": "Grant the main window access to required core features and native dialog commands.", + "remote": { + "urls": ["http://127.0.0.1:*", "http://localhost:*"] + }, + "windows": ["main"], + "permissions": [ + "core:default", + "core:menu:default", + "dialog:allow-open", + "opener:allow-default-urls", + "core:webview:allow-set-webview-zoom" + ] +} diff --git a/packages/tauri-app/src-tauri/gen/schemas/acl-manifests.json b/packages/tauri-app/src-tauri/gen/schemas/acl-manifests.json new file mode 100644 index 0000000..60e4f04 --- /dev/null +++ b/packages/tauri-app/src-tauri/gen/schemas/acl-manifests.json @@ -0,0 +1 @@ +{"core":{"default_permission":{"identifier":"default","description":"Default core plugins set.","permissions":["core:path:default","core:event:default","core:window:default","core:webview:default","core:app:default","core:image:default","core:resources:default","core:menu:default","core:tray:default"]},"permissions":{},"permission_sets":{},"global_scope_schema":null},"core:app":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-version","allow-name","allow-tauri-version","allow-identifier","allow-bundle-type","allow-register-listener","allow-remove-listener"]},"permissions":{"allow-app-hide":{"identifier":"allow-app-hide","description":"Enables the app_hide command without any pre-configured scope.","commands":{"allow":["app_hide"],"deny":[]}},"allow-app-show":{"identifier":"allow-app-show","description":"Enables the app_show command without any pre-configured scope.","commands":{"allow":["app_show"],"deny":[]}},"allow-bundle-type":{"identifier":"allow-bundle-type","description":"Enables the bundle_type command without any pre-configured scope.","commands":{"allow":["bundle_type"],"deny":[]}},"allow-default-window-icon":{"identifier":"allow-default-window-icon","description":"Enables the default_window_icon command without any pre-configured scope.","commands":{"allow":["default_window_icon"],"deny":[]}},"allow-fetch-data-store-identifiers":{"identifier":"allow-fetch-data-store-identifiers","description":"Enables the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":["fetch_data_store_identifiers"],"deny":[]}},"allow-identifier":{"identifier":"allow-identifier","description":"Enables the identifier command without any pre-configured scope.","commands":{"allow":["identifier"],"deny":[]}},"allow-name":{"identifier":"allow-name","description":"Enables the name command without any pre-configured scope.","commands":{"allow":["name"],"deny":[]}},"allow-register-listener":{"identifier":"allow-register-listener","description":"Enables the register_listener command without any pre-configured scope.","commands":{"allow":["register_listener"],"deny":[]}},"allow-remove-data-store":{"identifier":"allow-remove-data-store","description":"Enables the remove_data_store command without any pre-configured scope.","commands":{"allow":["remove_data_store"],"deny":[]}},"allow-remove-listener":{"identifier":"allow-remove-listener","description":"Enables the remove_listener command without any pre-configured scope.","commands":{"allow":["remove_listener"],"deny":[]}},"allow-set-app-theme":{"identifier":"allow-set-app-theme","description":"Enables the set_app_theme command without any pre-configured scope.","commands":{"allow":["set_app_theme"],"deny":[]}},"allow-set-dock-visibility":{"identifier":"allow-set-dock-visibility","description":"Enables the set_dock_visibility command without any pre-configured scope.","commands":{"allow":["set_dock_visibility"],"deny":[]}},"allow-tauri-version":{"identifier":"allow-tauri-version","description":"Enables the tauri_version command without any pre-configured scope.","commands":{"allow":["tauri_version"],"deny":[]}},"allow-version":{"identifier":"allow-version","description":"Enables the version command without any pre-configured scope.","commands":{"allow":["version"],"deny":[]}},"deny-app-hide":{"identifier":"deny-app-hide","description":"Denies the app_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["app_hide"]}},"deny-app-show":{"identifier":"deny-app-show","description":"Denies the app_show command without any pre-configured scope.","commands":{"allow":[],"deny":["app_show"]}},"deny-bundle-type":{"identifier":"deny-bundle-type","description":"Denies the bundle_type command without any pre-configured scope.","commands":{"allow":[],"deny":["bundle_type"]}},"deny-default-window-icon":{"identifier":"deny-default-window-icon","description":"Denies the default_window_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["default_window_icon"]}},"deny-fetch-data-store-identifiers":{"identifier":"deny-fetch-data-store-identifiers","description":"Denies the fetch_data_store_identifiers command without any pre-configured scope.","commands":{"allow":[],"deny":["fetch_data_store_identifiers"]}},"deny-identifier":{"identifier":"deny-identifier","description":"Denies the identifier command without any pre-configured scope.","commands":{"allow":[],"deny":["identifier"]}},"deny-name":{"identifier":"deny-name","description":"Denies the name command without any pre-configured scope.","commands":{"allow":[],"deny":["name"]}},"deny-register-listener":{"identifier":"deny-register-listener","description":"Denies the register_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["register_listener"]}},"deny-remove-data-store":{"identifier":"deny-remove-data-store","description":"Denies the remove_data_store command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_data_store"]}},"deny-remove-listener":{"identifier":"deny-remove-listener","description":"Denies the remove_listener command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_listener"]}},"deny-set-app-theme":{"identifier":"deny-set-app-theme","description":"Denies the set_app_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_app_theme"]}},"deny-set-dock-visibility":{"identifier":"deny-set-dock-visibility","description":"Denies the set_dock_visibility command without any pre-configured scope.","commands":{"allow":[],"deny":["set_dock_visibility"]}},"deny-tauri-version":{"identifier":"deny-tauri-version","description":"Denies the tauri_version command without any pre-configured scope.","commands":{"allow":[],"deny":["tauri_version"]}},"deny-version":{"identifier":"deny-version","description":"Denies the version command without any pre-configured scope.","commands":{"allow":[],"deny":["version"]}}},"permission_sets":{},"global_scope_schema":null},"core:event":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-listen","allow-unlisten","allow-emit","allow-emit-to"]},"permissions":{"allow-emit":{"identifier":"allow-emit","description":"Enables the emit command without any pre-configured scope.","commands":{"allow":["emit"],"deny":[]}},"allow-emit-to":{"identifier":"allow-emit-to","description":"Enables the emit_to command without any pre-configured scope.","commands":{"allow":["emit_to"],"deny":[]}},"allow-listen":{"identifier":"allow-listen","description":"Enables the listen command without any pre-configured scope.","commands":{"allow":["listen"],"deny":[]}},"allow-unlisten":{"identifier":"allow-unlisten","description":"Enables the unlisten command without any pre-configured scope.","commands":{"allow":["unlisten"],"deny":[]}},"deny-emit":{"identifier":"deny-emit","description":"Denies the emit command without any pre-configured scope.","commands":{"allow":[],"deny":["emit"]}},"deny-emit-to":{"identifier":"deny-emit-to","description":"Denies the emit_to command without any pre-configured scope.","commands":{"allow":[],"deny":["emit_to"]}},"deny-listen":{"identifier":"deny-listen","description":"Denies the listen command without any pre-configured scope.","commands":{"allow":[],"deny":["listen"]}},"deny-unlisten":{"identifier":"deny-unlisten","description":"Denies the unlisten command without any pre-configured scope.","commands":{"allow":[],"deny":["unlisten"]}}},"permission_sets":{},"global_scope_schema":null},"core:image":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-from-bytes","allow-from-path","allow-rgba","allow-size"]},"permissions":{"allow-from-bytes":{"identifier":"allow-from-bytes","description":"Enables the from_bytes command without any pre-configured scope.","commands":{"allow":["from_bytes"],"deny":[]}},"allow-from-path":{"identifier":"allow-from-path","description":"Enables the from_path command without any pre-configured scope.","commands":{"allow":["from_path"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-rgba":{"identifier":"allow-rgba","description":"Enables the rgba command without any pre-configured scope.","commands":{"allow":["rgba"],"deny":[]}},"allow-size":{"identifier":"allow-size","description":"Enables the size command without any pre-configured scope.","commands":{"allow":["size"],"deny":[]}},"deny-from-bytes":{"identifier":"deny-from-bytes","description":"Denies the from_bytes command without any pre-configured scope.","commands":{"allow":[],"deny":["from_bytes"]}},"deny-from-path":{"identifier":"deny-from-path","description":"Denies the from_path command without any pre-configured scope.","commands":{"allow":[],"deny":["from_path"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-rgba":{"identifier":"deny-rgba","description":"Denies the rgba command without any pre-configured scope.","commands":{"allow":[],"deny":["rgba"]}},"deny-size":{"identifier":"deny-size","description":"Denies the size command without any pre-configured scope.","commands":{"allow":[],"deny":["size"]}}},"permission_sets":{},"global_scope_schema":null},"core:menu":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-append","allow-prepend","allow-insert","allow-remove","allow-remove-at","allow-items","allow-get","allow-popup","allow-create-default","allow-set-as-app-menu","allow-set-as-window-menu","allow-text","allow-set-text","allow-is-enabled","allow-set-enabled","allow-set-accelerator","allow-set-as-windows-menu-for-nsapp","allow-set-as-help-menu-for-nsapp","allow-is-checked","allow-set-checked","allow-set-icon"]},"permissions":{"allow-append":{"identifier":"allow-append","description":"Enables the append command without any pre-configured scope.","commands":{"allow":["append"],"deny":[]}},"allow-create-default":{"identifier":"allow-create-default","description":"Enables the create_default command without any pre-configured scope.","commands":{"allow":["create_default"],"deny":[]}},"allow-get":{"identifier":"allow-get","description":"Enables the get command without any pre-configured scope.","commands":{"allow":["get"],"deny":[]}},"allow-insert":{"identifier":"allow-insert","description":"Enables the insert command without any pre-configured scope.","commands":{"allow":["insert"],"deny":[]}},"allow-is-checked":{"identifier":"allow-is-checked","description":"Enables the is_checked command without any pre-configured scope.","commands":{"allow":["is_checked"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-items":{"identifier":"allow-items","description":"Enables the items command without any pre-configured scope.","commands":{"allow":["items"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-popup":{"identifier":"allow-popup","description":"Enables the popup command without any pre-configured scope.","commands":{"allow":["popup"],"deny":[]}},"allow-prepend":{"identifier":"allow-prepend","description":"Enables the prepend command without any pre-configured scope.","commands":{"allow":["prepend"],"deny":[]}},"allow-remove":{"identifier":"allow-remove","description":"Enables the remove command without any pre-configured scope.","commands":{"allow":["remove"],"deny":[]}},"allow-remove-at":{"identifier":"allow-remove-at","description":"Enables the remove_at command without any pre-configured scope.","commands":{"allow":["remove_at"],"deny":[]}},"allow-set-accelerator":{"identifier":"allow-set-accelerator","description":"Enables the set_accelerator command without any pre-configured scope.","commands":{"allow":["set_accelerator"],"deny":[]}},"allow-set-as-app-menu":{"identifier":"allow-set-as-app-menu","description":"Enables the set_as_app_menu command without any pre-configured scope.","commands":{"allow":["set_as_app_menu"],"deny":[]}},"allow-set-as-help-menu-for-nsapp":{"identifier":"allow-set-as-help-menu-for-nsapp","description":"Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_help_menu_for_nsapp"],"deny":[]}},"allow-set-as-window-menu":{"identifier":"allow-set-as-window-menu","description":"Enables the set_as_window_menu command without any pre-configured scope.","commands":{"allow":["set_as_window_menu"],"deny":[]}},"allow-set-as-windows-menu-for-nsapp":{"identifier":"allow-set-as-windows-menu-for-nsapp","description":"Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":["set_as_windows_menu_for_nsapp"],"deny":[]}},"allow-set-checked":{"identifier":"allow-set-checked","description":"Enables the set_checked command without any pre-configured scope.","commands":{"allow":["set_checked"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-text":{"identifier":"allow-set-text","description":"Enables the set_text command without any pre-configured scope.","commands":{"allow":["set_text"],"deny":[]}},"allow-text":{"identifier":"allow-text","description":"Enables the text command without any pre-configured scope.","commands":{"allow":["text"],"deny":[]}},"deny-append":{"identifier":"deny-append","description":"Denies the append command without any pre-configured scope.","commands":{"allow":[],"deny":["append"]}},"deny-create-default":{"identifier":"deny-create-default","description":"Denies the create_default command without any pre-configured scope.","commands":{"allow":[],"deny":["create_default"]}},"deny-get":{"identifier":"deny-get","description":"Denies the get command without any pre-configured scope.","commands":{"allow":[],"deny":["get"]}},"deny-insert":{"identifier":"deny-insert","description":"Denies the insert command without any pre-configured scope.","commands":{"allow":[],"deny":["insert"]}},"deny-is-checked":{"identifier":"deny-is-checked","description":"Denies the is_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["is_checked"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-items":{"identifier":"deny-items","description":"Denies the items command without any pre-configured scope.","commands":{"allow":[],"deny":["items"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-popup":{"identifier":"deny-popup","description":"Denies the popup command without any pre-configured scope.","commands":{"allow":[],"deny":["popup"]}},"deny-prepend":{"identifier":"deny-prepend","description":"Denies the prepend command without any pre-configured scope.","commands":{"allow":[],"deny":["prepend"]}},"deny-remove":{"identifier":"deny-remove","description":"Denies the remove command without any pre-configured scope.","commands":{"allow":[],"deny":["remove"]}},"deny-remove-at":{"identifier":"deny-remove-at","description":"Denies the remove_at command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_at"]}},"deny-set-accelerator":{"identifier":"deny-set-accelerator","description":"Denies the set_accelerator command without any pre-configured scope.","commands":{"allow":[],"deny":["set_accelerator"]}},"deny-set-as-app-menu":{"identifier":"deny-set-as-app-menu","description":"Denies the set_as_app_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_app_menu"]}},"deny-set-as-help-menu-for-nsapp":{"identifier":"deny-set-as-help-menu-for-nsapp","description":"Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_help_menu_for_nsapp"]}},"deny-set-as-window-menu":{"identifier":"deny-set-as-window-menu","description":"Denies the set_as_window_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_window_menu"]}},"deny-set-as-windows-menu-for-nsapp":{"identifier":"deny-set-as-windows-menu-for-nsapp","description":"Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.","commands":{"allow":[],"deny":["set_as_windows_menu_for_nsapp"]}},"deny-set-checked":{"identifier":"deny-set-checked","description":"Denies the set_checked command without any pre-configured scope.","commands":{"allow":[],"deny":["set_checked"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-text":{"identifier":"deny-set-text","description":"Denies the set_text command without any pre-configured scope.","commands":{"allow":[],"deny":["set_text"]}},"deny-text":{"identifier":"deny-text","description":"Denies the text command without any pre-configured scope.","commands":{"allow":[],"deny":["text"]}}},"permission_sets":{},"global_scope_schema":null},"core:path":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-resolve-directory","allow-resolve","allow-normalize","allow-join","allow-dirname","allow-extname","allow-basename","allow-is-absolute"]},"permissions":{"allow-basename":{"identifier":"allow-basename","description":"Enables the basename command without any pre-configured scope.","commands":{"allow":["basename"],"deny":[]}},"allow-dirname":{"identifier":"allow-dirname","description":"Enables the dirname command without any pre-configured scope.","commands":{"allow":["dirname"],"deny":[]}},"allow-extname":{"identifier":"allow-extname","description":"Enables the extname command without any pre-configured scope.","commands":{"allow":["extname"],"deny":[]}},"allow-is-absolute":{"identifier":"allow-is-absolute","description":"Enables the is_absolute command without any pre-configured scope.","commands":{"allow":["is_absolute"],"deny":[]}},"allow-join":{"identifier":"allow-join","description":"Enables the join command without any pre-configured scope.","commands":{"allow":["join"],"deny":[]}},"allow-normalize":{"identifier":"allow-normalize","description":"Enables the normalize command without any pre-configured scope.","commands":{"allow":["normalize"],"deny":[]}},"allow-resolve":{"identifier":"allow-resolve","description":"Enables the resolve command without any pre-configured scope.","commands":{"allow":["resolve"],"deny":[]}},"allow-resolve-directory":{"identifier":"allow-resolve-directory","description":"Enables the resolve_directory command without any pre-configured scope.","commands":{"allow":["resolve_directory"],"deny":[]}},"deny-basename":{"identifier":"deny-basename","description":"Denies the basename command without any pre-configured scope.","commands":{"allow":[],"deny":["basename"]}},"deny-dirname":{"identifier":"deny-dirname","description":"Denies the dirname command without any pre-configured scope.","commands":{"allow":[],"deny":["dirname"]}},"deny-extname":{"identifier":"deny-extname","description":"Denies the extname command without any pre-configured scope.","commands":{"allow":[],"deny":["extname"]}},"deny-is-absolute":{"identifier":"deny-is-absolute","description":"Denies the is_absolute command without any pre-configured scope.","commands":{"allow":[],"deny":["is_absolute"]}},"deny-join":{"identifier":"deny-join","description":"Denies the join command without any pre-configured scope.","commands":{"allow":[],"deny":["join"]}},"deny-normalize":{"identifier":"deny-normalize","description":"Denies the normalize command without any pre-configured scope.","commands":{"allow":[],"deny":["normalize"]}},"deny-resolve":{"identifier":"deny-resolve","description":"Denies the resolve command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve"]}},"deny-resolve-directory":{"identifier":"deny-resolve-directory","description":"Denies the resolve_directory command without any pre-configured scope.","commands":{"allow":[],"deny":["resolve_directory"]}}},"permission_sets":{},"global_scope_schema":null},"core:resources":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-close"]},"permissions":{"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}}},"permission_sets":{},"global_scope_schema":null},"core:tray":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin, which enables all commands.","permissions":["allow-new","allow-get-by-id","allow-remove-by-id","allow-set-icon","allow-set-menu","allow-set-tooltip","allow-set-title","allow-set-visible","allow-set-temp-dir-path","allow-set-icon-as-template","allow-set-show-menu-on-left-click"]},"permissions":{"allow-get-by-id":{"identifier":"allow-get-by-id","description":"Enables the get_by_id command without any pre-configured scope.","commands":{"allow":["get_by_id"],"deny":[]}},"allow-new":{"identifier":"allow-new","description":"Enables the new command without any pre-configured scope.","commands":{"allow":["new"],"deny":[]}},"allow-remove-by-id":{"identifier":"allow-remove-by-id","description":"Enables the remove_by_id command without any pre-configured scope.","commands":{"allow":["remove_by_id"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-icon-as-template":{"identifier":"allow-set-icon-as-template","description":"Enables the set_icon_as_template command without any pre-configured scope.","commands":{"allow":["set_icon_as_template"],"deny":[]}},"allow-set-menu":{"identifier":"allow-set-menu","description":"Enables the set_menu command without any pre-configured scope.","commands":{"allow":["set_menu"],"deny":[]}},"allow-set-show-menu-on-left-click":{"identifier":"allow-set-show-menu-on-left-click","description":"Enables the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":["set_show_menu_on_left_click"],"deny":[]}},"allow-set-temp-dir-path":{"identifier":"allow-set-temp-dir-path","description":"Enables the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":["set_temp_dir_path"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-tooltip":{"identifier":"allow-set-tooltip","description":"Enables the set_tooltip command without any pre-configured scope.","commands":{"allow":["set_tooltip"],"deny":[]}},"allow-set-visible":{"identifier":"allow-set-visible","description":"Enables the set_visible command without any pre-configured scope.","commands":{"allow":["set_visible"],"deny":[]}},"deny-get-by-id":{"identifier":"deny-get-by-id","description":"Denies the get_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["get_by_id"]}},"deny-new":{"identifier":"deny-new","description":"Denies the new command without any pre-configured scope.","commands":{"allow":[],"deny":["new"]}},"deny-remove-by-id":{"identifier":"deny-remove-by-id","description":"Denies the remove_by_id command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_by_id"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-icon-as-template":{"identifier":"deny-set-icon-as-template","description":"Denies the set_icon_as_template command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon_as_template"]}},"deny-set-menu":{"identifier":"deny-set-menu","description":"Denies the set_menu command without any pre-configured scope.","commands":{"allow":[],"deny":["set_menu"]}},"deny-set-show-menu-on-left-click":{"identifier":"deny-set-show-menu-on-left-click","description":"Denies the set_show_menu_on_left_click command without any pre-configured scope.","commands":{"allow":[],"deny":["set_show_menu_on_left_click"]}},"deny-set-temp-dir-path":{"identifier":"deny-set-temp-dir-path","description":"Denies the set_temp_dir_path command without any pre-configured scope.","commands":{"allow":[],"deny":["set_temp_dir_path"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-tooltip":{"identifier":"deny-set-tooltip","description":"Denies the set_tooltip command without any pre-configured scope.","commands":{"allow":[],"deny":["set_tooltip"]}},"deny-set-visible":{"identifier":"deny-set-visible","description":"Denies the set_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible"]}}},"permission_sets":{},"global_scope_schema":null},"core:webview":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-webviews","allow-webview-position","allow-webview-size","allow-internal-toggle-devtools"]},"permissions":{"allow-clear-all-browsing-data":{"identifier":"allow-clear-all-browsing-data","description":"Enables the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":["clear_all_browsing_data"],"deny":[]}},"allow-create-webview":{"identifier":"allow-create-webview","description":"Enables the create_webview command without any pre-configured scope.","commands":{"allow":["create_webview"],"deny":[]}},"allow-create-webview-window":{"identifier":"allow-create-webview-window","description":"Enables the create_webview_window command without any pre-configured scope.","commands":{"allow":["create_webview_window"],"deny":[]}},"allow-get-all-webviews":{"identifier":"allow-get-all-webviews","description":"Enables the get_all_webviews command without any pre-configured scope.","commands":{"allow":["get_all_webviews"],"deny":[]}},"allow-internal-toggle-devtools":{"identifier":"allow-internal-toggle-devtools","description":"Enables the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":["internal_toggle_devtools"],"deny":[]}},"allow-print":{"identifier":"allow-print","description":"Enables the print command without any pre-configured scope.","commands":{"allow":["print"],"deny":[]}},"allow-reparent":{"identifier":"allow-reparent","description":"Enables the reparent command without any pre-configured scope.","commands":{"allow":["reparent"],"deny":[]}},"allow-set-webview-auto-resize":{"identifier":"allow-set-webview-auto-resize","description":"Enables the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":["set_webview_auto_resize"],"deny":[]}},"allow-set-webview-background-color":{"identifier":"allow-set-webview-background-color","description":"Enables the set_webview_background_color command without any pre-configured scope.","commands":{"allow":["set_webview_background_color"],"deny":[]}},"allow-set-webview-focus":{"identifier":"allow-set-webview-focus","description":"Enables the set_webview_focus command without any pre-configured scope.","commands":{"allow":["set_webview_focus"],"deny":[]}},"allow-set-webview-position":{"identifier":"allow-set-webview-position","description":"Enables the set_webview_position command without any pre-configured scope.","commands":{"allow":["set_webview_position"],"deny":[]}},"allow-set-webview-size":{"identifier":"allow-set-webview-size","description":"Enables the set_webview_size command without any pre-configured scope.","commands":{"allow":["set_webview_size"],"deny":[]}},"allow-set-webview-zoom":{"identifier":"allow-set-webview-zoom","description":"Enables the set_webview_zoom command without any pre-configured scope.","commands":{"allow":["set_webview_zoom"],"deny":[]}},"allow-webview-close":{"identifier":"allow-webview-close","description":"Enables the webview_close command without any pre-configured scope.","commands":{"allow":["webview_close"],"deny":[]}},"allow-webview-hide":{"identifier":"allow-webview-hide","description":"Enables the webview_hide command without any pre-configured scope.","commands":{"allow":["webview_hide"],"deny":[]}},"allow-webview-position":{"identifier":"allow-webview-position","description":"Enables the webview_position command without any pre-configured scope.","commands":{"allow":["webview_position"],"deny":[]}},"allow-webview-show":{"identifier":"allow-webview-show","description":"Enables the webview_show command without any pre-configured scope.","commands":{"allow":["webview_show"],"deny":[]}},"allow-webview-size":{"identifier":"allow-webview-size","description":"Enables the webview_size command without any pre-configured scope.","commands":{"allow":["webview_size"],"deny":[]}},"deny-clear-all-browsing-data":{"identifier":"deny-clear-all-browsing-data","description":"Denies the clear_all_browsing_data command without any pre-configured scope.","commands":{"allow":[],"deny":["clear_all_browsing_data"]}},"deny-create-webview":{"identifier":"deny-create-webview","description":"Denies the create_webview command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview"]}},"deny-create-webview-window":{"identifier":"deny-create-webview-window","description":"Denies the create_webview_window command without any pre-configured scope.","commands":{"allow":[],"deny":["create_webview_window"]}},"deny-get-all-webviews":{"identifier":"deny-get-all-webviews","description":"Denies the get_all_webviews command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_webviews"]}},"deny-internal-toggle-devtools":{"identifier":"deny-internal-toggle-devtools","description":"Denies the internal_toggle_devtools command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_devtools"]}},"deny-print":{"identifier":"deny-print","description":"Denies the print command without any pre-configured scope.","commands":{"allow":[],"deny":["print"]}},"deny-reparent":{"identifier":"deny-reparent","description":"Denies the reparent command without any pre-configured scope.","commands":{"allow":[],"deny":["reparent"]}},"deny-set-webview-auto-resize":{"identifier":"deny-set-webview-auto-resize","description":"Denies the set_webview_auto_resize command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_auto_resize"]}},"deny-set-webview-background-color":{"identifier":"deny-set-webview-background-color","description":"Denies the set_webview_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_background_color"]}},"deny-set-webview-focus":{"identifier":"deny-set-webview-focus","description":"Denies the set_webview_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_focus"]}},"deny-set-webview-position":{"identifier":"deny-set-webview-position","description":"Denies the set_webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_position"]}},"deny-set-webview-size":{"identifier":"deny-set-webview-size","description":"Denies the set_webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_size"]}},"deny-set-webview-zoom":{"identifier":"deny-set-webview-zoom","description":"Denies the set_webview_zoom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_webview_zoom"]}},"deny-webview-close":{"identifier":"deny-webview-close","description":"Denies the webview_close command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_close"]}},"deny-webview-hide":{"identifier":"deny-webview-hide","description":"Denies the webview_hide command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_hide"]}},"deny-webview-position":{"identifier":"deny-webview-position","description":"Denies the webview_position command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_position"]}},"deny-webview-show":{"identifier":"deny-webview-show","description":"Denies the webview_show command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_show"]}},"deny-webview-size":{"identifier":"deny-webview-size","description":"Denies the webview_size command without any pre-configured scope.","commands":{"allow":[],"deny":["webview_size"]}}},"permission_sets":{},"global_scope_schema":null},"core:window":{"default_permission":{"identifier":"default","description":"Default permissions for the plugin.","permissions":["allow-get-all-windows","allow-scale-factor","allow-inner-position","allow-outer-position","allow-inner-size","allow-outer-size","allow-is-fullscreen","allow-is-minimized","allow-is-maximized","allow-is-focused","allow-is-decorated","allow-is-resizable","allow-is-maximizable","allow-is-minimizable","allow-is-closable","allow-is-visible","allow-is-enabled","allow-title","allow-current-monitor","allow-primary-monitor","allow-monitor-from-point","allow-available-monitors","allow-cursor-position","allow-theme","allow-is-always-on-top","allow-internal-toggle-maximize"]},"permissions":{"allow-available-monitors":{"identifier":"allow-available-monitors","description":"Enables the available_monitors command without any pre-configured scope.","commands":{"allow":["available_monitors"],"deny":[]}},"allow-center":{"identifier":"allow-center","description":"Enables the center command without any pre-configured scope.","commands":{"allow":["center"],"deny":[]}},"allow-close":{"identifier":"allow-close","description":"Enables the close command without any pre-configured scope.","commands":{"allow":["close"],"deny":[]}},"allow-create":{"identifier":"allow-create","description":"Enables the create command without any pre-configured scope.","commands":{"allow":["create"],"deny":[]}},"allow-current-monitor":{"identifier":"allow-current-monitor","description":"Enables the current_monitor command without any pre-configured scope.","commands":{"allow":["current_monitor"],"deny":[]}},"allow-cursor-position":{"identifier":"allow-cursor-position","description":"Enables the cursor_position command without any pre-configured scope.","commands":{"allow":["cursor_position"],"deny":[]}},"allow-destroy":{"identifier":"allow-destroy","description":"Enables the destroy command without any pre-configured scope.","commands":{"allow":["destroy"],"deny":[]}},"allow-get-all-windows":{"identifier":"allow-get-all-windows","description":"Enables the get_all_windows command without any pre-configured scope.","commands":{"allow":["get_all_windows"],"deny":[]}},"allow-hide":{"identifier":"allow-hide","description":"Enables the hide command without any pre-configured scope.","commands":{"allow":["hide"],"deny":[]}},"allow-inner-position":{"identifier":"allow-inner-position","description":"Enables the inner_position command without any pre-configured scope.","commands":{"allow":["inner_position"],"deny":[]}},"allow-inner-size":{"identifier":"allow-inner-size","description":"Enables the inner_size command without any pre-configured scope.","commands":{"allow":["inner_size"],"deny":[]}},"allow-internal-toggle-maximize":{"identifier":"allow-internal-toggle-maximize","description":"Enables the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":["internal_toggle_maximize"],"deny":[]}},"allow-is-always-on-top":{"identifier":"allow-is-always-on-top","description":"Enables the is_always_on_top command without any pre-configured scope.","commands":{"allow":["is_always_on_top"],"deny":[]}},"allow-is-closable":{"identifier":"allow-is-closable","description":"Enables the is_closable command without any pre-configured scope.","commands":{"allow":["is_closable"],"deny":[]}},"allow-is-decorated":{"identifier":"allow-is-decorated","description":"Enables the is_decorated command without any pre-configured scope.","commands":{"allow":["is_decorated"],"deny":[]}},"allow-is-enabled":{"identifier":"allow-is-enabled","description":"Enables the is_enabled command without any pre-configured scope.","commands":{"allow":["is_enabled"],"deny":[]}},"allow-is-focused":{"identifier":"allow-is-focused","description":"Enables the is_focused command without any pre-configured scope.","commands":{"allow":["is_focused"],"deny":[]}},"allow-is-fullscreen":{"identifier":"allow-is-fullscreen","description":"Enables the is_fullscreen command without any pre-configured scope.","commands":{"allow":["is_fullscreen"],"deny":[]}},"allow-is-maximizable":{"identifier":"allow-is-maximizable","description":"Enables the is_maximizable command without any pre-configured scope.","commands":{"allow":["is_maximizable"],"deny":[]}},"allow-is-maximized":{"identifier":"allow-is-maximized","description":"Enables the is_maximized command without any pre-configured scope.","commands":{"allow":["is_maximized"],"deny":[]}},"allow-is-minimizable":{"identifier":"allow-is-minimizable","description":"Enables the is_minimizable command without any pre-configured scope.","commands":{"allow":["is_minimizable"],"deny":[]}},"allow-is-minimized":{"identifier":"allow-is-minimized","description":"Enables the is_minimized command without any pre-configured scope.","commands":{"allow":["is_minimized"],"deny":[]}},"allow-is-resizable":{"identifier":"allow-is-resizable","description":"Enables the is_resizable command without any pre-configured scope.","commands":{"allow":["is_resizable"],"deny":[]}},"allow-is-visible":{"identifier":"allow-is-visible","description":"Enables the is_visible command without any pre-configured scope.","commands":{"allow":["is_visible"],"deny":[]}},"allow-maximize":{"identifier":"allow-maximize","description":"Enables the maximize command without any pre-configured scope.","commands":{"allow":["maximize"],"deny":[]}},"allow-minimize":{"identifier":"allow-minimize","description":"Enables the minimize command without any pre-configured scope.","commands":{"allow":["minimize"],"deny":[]}},"allow-monitor-from-point":{"identifier":"allow-monitor-from-point","description":"Enables the monitor_from_point command without any pre-configured scope.","commands":{"allow":["monitor_from_point"],"deny":[]}},"allow-outer-position":{"identifier":"allow-outer-position","description":"Enables the outer_position command without any pre-configured scope.","commands":{"allow":["outer_position"],"deny":[]}},"allow-outer-size":{"identifier":"allow-outer-size","description":"Enables the outer_size command without any pre-configured scope.","commands":{"allow":["outer_size"],"deny":[]}},"allow-primary-monitor":{"identifier":"allow-primary-monitor","description":"Enables the primary_monitor command without any pre-configured scope.","commands":{"allow":["primary_monitor"],"deny":[]}},"allow-request-user-attention":{"identifier":"allow-request-user-attention","description":"Enables the request_user_attention command without any pre-configured scope.","commands":{"allow":["request_user_attention"],"deny":[]}},"allow-scale-factor":{"identifier":"allow-scale-factor","description":"Enables the scale_factor command without any pre-configured scope.","commands":{"allow":["scale_factor"],"deny":[]}},"allow-set-always-on-bottom":{"identifier":"allow-set-always-on-bottom","description":"Enables the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":["set_always_on_bottom"],"deny":[]}},"allow-set-always-on-top":{"identifier":"allow-set-always-on-top","description":"Enables the set_always_on_top command without any pre-configured scope.","commands":{"allow":["set_always_on_top"],"deny":[]}},"allow-set-background-color":{"identifier":"allow-set-background-color","description":"Enables the set_background_color command without any pre-configured scope.","commands":{"allow":["set_background_color"],"deny":[]}},"allow-set-badge-count":{"identifier":"allow-set-badge-count","description":"Enables the set_badge_count command without any pre-configured scope.","commands":{"allow":["set_badge_count"],"deny":[]}},"allow-set-badge-label":{"identifier":"allow-set-badge-label","description":"Enables the set_badge_label command without any pre-configured scope.","commands":{"allow":["set_badge_label"],"deny":[]}},"allow-set-closable":{"identifier":"allow-set-closable","description":"Enables the set_closable command without any pre-configured scope.","commands":{"allow":["set_closable"],"deny":[]}},"allow-set-content-protected":{"identifier":"allow-set-content-protected","description":"Enables the set_content_protected command without any pre-configured scope.","commands":{"allow":["set_content_protected"],"deny":[]}},"allow-set-cursor-grab":{"identifier":"allow-set-cursor-grab","description":"Enables the set_cursor_grab command without any pre-configured scope.","commands":{"allow":["set_cursor_grab"],"deny":[]}},"allow-set-cursor-icon":{"identifier":"allow-set-cursor-icon","description":"Enables the set_cursor_icon command without any pre-configured scope.","commands":{"allow":["set_cursor_icon"],"deny":[]}},"allow-set-cursor-position":{"identifier":"allow-set-cursor-position","description":"Enables the set_cursor_position command without any pre-configured scope.","commands":{"allow":["set_cursor_position"],"deny":[]}},"allow-set-cursor-visible":{"identifier":"allow-set-cursor-visible","description":"Enables the set_cursor_visible command without any pre-configured scope.","commands":{"allow":["set_cursor_visible"],"deny":[]}},"allow-set-decorations":{"identifier":"allow-set-decorations","description":"Enables the set_decorations command without any pre-configured scope.","commands":{"allow":["set_decorations"],"deny":[]}},"allow-set-effects":{"identifier":"allow-set-effects","description":"Enables the set_effects command without any pre-configured scope.","commands":{"allow":["set_effects"],"deny":[]}},"allow-set-enabled":{"identifier":"allow-set-enabled","description":"Enables the set_enabled command without any pre-configured scope.","commands":{"allow":["set_enabled"],"deny":[]}},"allow-set-focus":{"identifier":"allow-set-focus","description":"Enables the set_focus command without any pre-configured scope.","commands":{"allow":["set_focus"],"deny":[]}},"allow-set-focusable":{"identifier":"allow-set-focusable","description":"Enables the set_focusable command without any pre-configured scope.","commands":{"allow":["set_focusable"],"deny":[]}},"allow-set-fullscreen":{"identifier":"allow-set-fullscreen","description":"Enables the set_fullscreen command without any pre-configured scope.","commands":{"allow":["set_fullscreen"],"deny":[]}},"allow-set-icon":{"identifier":"allow-set-icon","description":"Enables the set_icon command without any pre-configured scope.","commands":{"allow":["set_icon"],"deny":[]}},"allow-set-ignore-cursor-events":{"identifier":"allow-set-ignore-cursor-events","description":"Enables the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":["set_ignore_cursor_events"],"deny":[]}},"allow-set-max-size":{"identifier":"allow-set-max-size","description":"Enables the set_max_size command without any pre-configured scope.","commands":{"allow":["set_max_size"],"deny":[]}},"allow-set-maximizable":{"identifier":"allow-set-maximizable","description":"Enables the set_maximizable command without any pre-configured scope.","commands":{"allow":["set_maximizable"],"deny":[]}},"allow-set-min-size":{"identifier":"allow-set-min-size","description":"Enables the set_min_size command without any pre-configured scope.","commands":{"allow":["set_min_size"],"deny":[]}},"allow-set-minimizable":{"identifier":"allow-set-minimizable","description":"Enables the set_minimizable command without any pre-configured scope.","commands":{"allow":["set_minimizable"],"deny":[]}},"allow-set-overlay-icon":{"identifier":"allow-set-overlay-icon","description":"Enables the set_overlay_icon command without any pre-configured scope.","commands":{"allow":["set_overlay_icon"],"deny":[]}},"allow-set-position":{"identifier":"allow-set-position","description":"Enables the set_position command without any pre-configured scope.","commands":{"allow":["set_position"],"deny":[]}},"allow-set-progress-bar":{"identifier":"allow-set-progress-bar","description":"Enables the set_progress_bar command without any pre-configured scope.","commands":{"allow":["set_progress_bar"],"deny":[]}},"allow-set-resizable":{"identifier":"allow-set-resizable","description":"Enables the set_resizable command without any pre-configured scope.","commands":{"allow":["set_resizable"],"deny":[]}},"allow-set-shadow":{"identifier":"allow-set-shadow","description":"Enables the set_shadow command without any pre-configured scope.","commands":{"allow":["set_shadow"],"deny":[]}},"allow-set-simple-fullscreen":{"identifier":"allow-set-simple-fullscreen","description":"Enables the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":["set_simple_fullscreen"],"deny":[]}},"allow-set-size":{"identifier":"allow-set-size","description":"Enables the set_size command without any pre-configured scope.","commands":{"allow":["set_size"],"deny":[]}},"allow-set-size-constraints":{"identifier":"allow-set-size-constraints","description":"Enables the set_size_constraints command without any pre-configured scope.","commands":{"allow":["set_size_constraints"],"deny":[]}},"allow-set-skip-taskbar":{"identifier":"allow-set-skip-taskbar","description":"Enables the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":["set_skip_taskbar"],"deny":[]}},"allow-set-theme":{"identifier":"allow-set-theme","description":"Enables the set_theme command without any pre-configured scope.","commands":{"allow":["set_theme"],"deny":[]}},"allow-set-title":{"identifier":"allow-set-title","description":"Enables the set_title command without any pre-configured scope.","commands":{"allow":["set_title"],"deny":[]}},"allow-set-title-bar-style":{"identifier":"allow-set-title-bar-style","description":"Enables the set_title_bar_style command without any pre-configured scope.","commands":{"allow":["set_title_bar_style"],"deny":[]}},"allow-set-visible-on-all-workspaces":{"identifier":"allow-set-visible-on-all-workspaces","description":"Enables the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":["set_visible_on_all_workspaces"],"deny":[]}},"allow-show":{"identifier":"allow-show","description":"Enables the show command without any pre-configured scope.","commands":{"allow":["show"],"deny":[]}},"allow-start-dragging":{"identifier":"allow-start-dragging","description":"Enables the start_dragging command without any pre-configured scope.","commands":{"allow":["start_dragging"],"deny":[]}},"allow-start-resize-dragging":{"identifier":"allow-start-resize-dragging","description":"Enables the start_resize_dragging command without any pre-configured scope.","commands":{"allow":["start_resize_dragging"],"deny":[]}},"allow-theme":{"identifier":"allow-theme","description":"Enables the theme command without any pre-configured scope.","commands":{"allow":["theme"],"deny":[]}},"allow-title":{"identifier":"allow-title","description":"Enables the title command without any pre-configured scope.","commands":{"allow":["title"],"deny":[]}},"allow-toggle-maximize":{"identifier":"allow-toggle-maximize","description":"Enables the toggle_maximize command without any pre-configured scope.","commands":{"allow":["toggle_maximize"],"deny":[]}},"allow-unmaximize":{"identifier":"allow-unmaximize","description":"Enables the unmaximize command without any pre-configured scope.","commands":{"allow":["unmaximize"],"deny":[]}},"allow-unminimize":{"identifier":"allow-unminimize","description":"Enables the unminimize command without any pre-configured scope.","commands":{"allow":["unminimize"],"deny":[]}},"deny-available-monitors":{"identifier":"deny-available-monitors","description":"Denies the available_monitors command without any pre-configured scope.","commands":{"allow":[],"deny":["available_monitors"]}},"deny-center":{"identifier":"deny-center","description":"Denies the center command without any pre-configured scope.","commands":{"allow":[],"deny":["center"]}},"deny-close":{"identifier":"deny-close","description":"Denies the close command without any pre-configured scope.","commands":{"allow":[],"deny":["close"]}},"deny-create":{"identifier":"deny-create","description":"Denies the create command without any pre-configured scope.","commands":{"allow":[],"deny":["create"]}},"deny-current-monitor":{"identifier":"deny-current-monitor","description":"Denies the current_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["current_monitor"]}},"deny-cursor-position":{"identifier":"deny-cursor-position","description":"Denies the cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["cursor_position"]}},"deny-destroy":{"identifier":"deny-destroy","description":"Denies the destroy command without any pre-configured scope.","commands":{"allow":[],"deny":["destroy"]}},"deny-get-all-windows":{"identifier":"deny-get-all-windows","description":"Denies the get_all_windows command without any pre-configured scope.","commands":{"allow":[],"deny":["get_all_windows"]}},"deny-hide":{"identifier":"deny-hide","description":"Denies the hide command without any pre-configured scope.","commands":{"allow":[],"deny":["hide"]}},"deny-inner-position":{"identifier":"deny-inner-position","description":"Denies the inner_position command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_position"]}},"deny-inner-size":{"identifier":"deny-inner-size","description":"Denies the inner_size command without any pre-configured scope.","commands":{"allow":[],"deny":["inner_size"]}},"deny-internal-toggle-maximize":{"identifier":"deny-internal-toggle-maximize","description":"Denies the internal_toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["internal_toggle_maximize"]}},"deny-is-always-on-top":{"identifier":"deny-is-always-on-top","description":"Denies the is_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["is_always_on_top"]}},"deny-is-closable":{"identifier":"deny-is-closable","description":"Denies the is_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_closable"]}},"deny-is-decorated":{"identifier":"deny-is-decorated","description":"Denies the is_decorated command without any pre-configured scope.","commands":{"allow":[],"deny":["is_decorated"]}},"deny-is-enabled":{"identifier":"deny-is-enabled","description":"Denies the is_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["is_enabled"]}},"deny-is-focused":{"identifier":"deny-is-focused","description":"Denies the is_focused command without any pre-configured scope.","commands":{"allow":[],"deny":["is_focused"]}},"deny-is-fullscreen":{"identifier":"deny-is-fullscreen","description":"Denies the is_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["is_fullscreen"]}},"deny-is-maximizable":{"identifier":"deny-is-maximizable","description":"Denies the is_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximizable"]}},"deny-is-maximized":{"identifier":"deny-is-maximized","description":"Denies the is_maximized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_maximized"]}},"deny-is-minimizable":{"identifier":"deny-is-minimizable","description":"Denies the is_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimizable"]}},"deny-is-minimized":{"identifier":"deny-is-minimized","description":"Denies the is_minimized command without any pre-configured scope.","commands":{"allow":[],"deny":["is_minimized"]}},"deny-is-resizable":{"identifier":"deny-is-resizable","description":"Denies the is_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["is_resizable"]}},"deny-is-visible":{"identifier":"deny-is-visible","description":"Denies the is_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["is_visible"]}},"deny-maximize":{"identifier":"deny-maximize","description":"Denies the maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["maximize"]}},"deny-minimize":{"identifier":"deny-minimize","description":"Denies the minimize command without any pre-configured scope.","commands":{"allow":[],"deny":["minimize"]}},"deny-monitor-from-point":{"identifier":"deny-monitor-from-point","description":"Denies the monitor_from_point command without any pre-configured scope.","commands":{"allow":[],"deny":["monitor_from_point"]}},"deny-outer-position":{"identifier":"deny-outer-position","description":"Denies the outer_position command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_position"]}},"deny-outer-size":{"identifier":"deny-outer-size","description":"Denies the outer_size command without any pre-configured scope.","commands":{"allow":[],"deny":["outer_size"]}},"deny-primary-monitor":{"identifier":"deny-primary-monitor","description":"Denies the primary_monitor command without any pre-configured scope.","commands":{"allow":[],"deny":["primary_monitor"]}},"deny-request-user-attention":{"identifier":"deny-request-user-attention","description":"Denies the request_user_attention command without any pre-configured scope.","commands":{"allow":[],"deny":["request_user_attention"]}},"deny-scale-factor":{"identifier":"deny-scale-factor","description":"Denies the scale_factor command without any pre-configured scope.","commands":{"allow":[],"deny":["scale_factor"]}},"deny-set-always-on-bottom":{"identifier":"deny-set-always-on-bottom","description":"Denies the set_always_on_bottom command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_bottom"]}},"deny-set-always-on-top":{"identifier":"deny-set-always-on-top","description":"Denies the set_always_on_top command without any pre-configured scope.","commands":{"allow":[],"deny":["set_always_on_top"]}},"deny-set-background-color":{"identifier":"deny-set-background-color","description":"Denies the set_background_color command without any pre-configured scope.","commands":{"allow":[],"deny":["set_background_color"]}},"deny-set-badge-count":{"identifier":"deny-set-badge-count","description":"Denies the set_badge_count command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_count"]}},"deny-set-badge-label":{"identifier":"deny-set-badge-label","description":"Denies the set_badge_label command without any pre-configured scope.","commands":{"allow":[],"deny":["set_badge_label"]}},"deny-set-closable":{"identifier":"deny-set-closable","description":"Denies the set_closable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_closable"]}},"deny-set-content-protected":{"identifier":"deny-set-content-protected","description":"Denies the set_content_protected command without any pre-configured scope.","commands":{"allow":[],"deny":["set_content_protected"]}},"deny-set-cursor-grab":{"identifier":"deny-set-cursor-grab","description":"Denies the set_cursor_grab command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_grab"]}},"deny-set-cursor-icon":{"identifier":"deny-set-cursor-icon","description":"Denies the set_cursor_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_icon"]}},"deny-set-cursor-position":{"identifier":"deny-set-cursor-position","description":"Denies the set_cursor_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_position"]}},"deny-set-cursor-visible":{"identifier":"deny-set-cursor-visible","description":"Denies the set_cursor_visible command without any pre-configured scope.","commands":{"allow":[],"deny":["set_cursor_visible"]}},"deny-set-decorations":{"identifier":"deny-set-decorations","description":"Denies the set_decorations command without any pre-configured scope.","commands":{"allow":[],"deny":["set_decorations"]}},"deny-set-effects":{"identifier":"deny-set-effects","description":"Denies the set_effects command without any pre-configured scope.","commands":{"allow":[],"deny":["set_effects"]}},"deny-set-enabled":{"identifier":"deny-set-enabled","description":"Denies the set_enabled command without any pre-configured scope.","commands":{"allow":[],"deny":["set_enabled"]}},"deny-set-focus":{"identifier":"deny-set-focus","description":"Denies the set_focus command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focus"]}},"deny-set-focusable":{"identifier":"deny-set-focusable","description":"Denies the set_focusable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_focusable"]}},"deny-set-fullscreen":{"identifier":"deny-set-fullscreen","description":"Denies the set_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_fullscreen"]}},"deny-set-icon":{"identifier":"deny-set-icon","description":"Denies the set_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_icon"]}},"deny-set-ignore-cursor-events":{"identifier":"deny-set-ignore-cursor-events","description":"Denies the set_ignore_cursor_events command without any pre-configured scope.","commands":{"allow":[],"deny":["set_ignore_cursor_events"]}},"deny-set-max-size":{"identifier":"deny-set-max-size","description":"Denies the set_max_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_max_size"]}},"deny-set-maximizable":{"identifier":"deny-set-maximizable","description":"Denies the set_maximizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_maximizable"]}},"deny-set-min-size":{"identifier":"deny-set-min-size","description":"Denies the set_min_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_min_size"]}},"deny-set-minimizable":{"identifier":"deny-set-minimizable","description":"Denies the set_minimizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_minimizable"]}},"deny-set-overlay-icon":{"identifier":"deny-set-overlay-icon","description":"Denies the set_overlay_icon command without any pre-configured scope.","commands":{"allow":[],"deny":["set_overlay_icon"]}},"deny-set-position":{"identifier":"deny-set-position","description":"Denies the set_position command without any pre-configured scope.","commands":{"allow":[],"deny":["set_position"]}},"deny-set-progress-bar":{"identifier":"deny-set-progress-bar","description":"Denies the set_progress_bar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_progress_bar"]}},"deny-set-resizable":{"identifier":"deny-set-resizable","description":"Denies the set_resizable command without any pre-configured scope.","commands":{"allow":[],"deny":["set_resizable"]}},"deny-set-shadow":{"identifier":"deny-set-shadow","description":"Denies the set_shadow command without any pre-configured scope.","commands":{"allow":[],"deny":["set_shadow"]}},"deny-set-simple-fullscreen":{"identifier":"deny-set-simple-fullscreen","description":"Denies the set_simple_fullscreen command without any pre-configured scope.","commands":{"allow":[],"deny":["set_simple_fullscreen"]}},"deny-set-size":{"identifier":"deny-set-size","description":"Denies the set_size command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size"]}},"deny-set-size-constraints":{"identifier":"deny-set-size-constraints","description":"Denies the set_size_constraints command without any pre-configured scope.","commands":{"allow":[],"deny":["set_size_constraints"]}},"deny-set-skip-taskbar":{"identifier":"deny-set-skip-taskbar","description":"Denies the set_skip_taskbar command without any pre-configured scope.","commands":{"allow":[],"deny":["set_skip_taskbar"]}},"deny-set-theme":{"identifier":"deny-set-theme","description":"Denies the set_theme command without any pre-configured scope.","commands":{"allow":[],"deny":["set_theme"]}},"deny-set-title":{"identifier":"deny-set-title","description":"Denies the set_title command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title"]}},"deny-set-title-bar-style":{"identifier":"deny-set-title-bar-style","description":"Denies the set_title_bar_style command without any pre-configured scope.","commands":{"allow":[],"deny":["set_title_bar_style"]}},"deny-set-visible-on-all-workspaces":{"identifier":"deny-set-visible-on-all-workspaces","description":"Denies the set_visible_on_all_workspaces command without any pre-configured scope.","commands":{"allow":[],"deny":["set_visible_on_all_workspaces"]}},"deny-show":{"identifier":"deny-show","description":"Denies the show command without any pre-configured scope.","commands":{"allow":[],"deny":["show"]}},"deny-start-dragging":{"identifier":"deny-start-dragging","description":"Denies the start_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_dragging"]}},"deny-start-resize-dragging":{"identifier":"deny-start-resize-dragging","description":"Denies the start_resize_dragging command without any pre-configured scope.","commands":{"allow":[],"deny":["start_resize_dragging"]}},"deny-theme":{"identifier":"deny-theme","description":"Denies the theme command without any pre-configured scope.","commands":{"allow":[],"deny":["theme"]}},"deny-title":{"identifier":"deny-title","description":"Denies the title command without any pre-configured scope.","commands":{"allow":[],"deny":["title"]}},"deny-toggle-maximize":{"identifier":"deny-toggle-maximize","description":"Denies the toggle_maximize command without any pre-configured scope.","commands":{"allow":[],"deny":["toggle_maximize"]}},"deny-unmaximize":{"identifier":"deny-unmaximize","description":"Denies the unmaximize command without any pre-configured scope.","commands":{"allow":[],"deny":["unmaximize"]}},"deny-unminimize":{"identifier":"deny-unminimize","description":"Denies the unminimize command without any pre-configured scope.","commands":{"allow":[],"deny":["unminimize"]}}},"permission_sets":{},"global_scope_schema":null},"dialog":{"default_permission":{"identifier":"default","description":"This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n","permissions":["allow-ask","allow-confirm","allow-message","allow-save","allow-open"]},"permissions":{"allow-ask":{"identifier":"allow-ask","description":"Enables the ask command without any pre-configured scope.","commands":{"allow":["ask"],"deny":[]}},"allow-confirm":{"identifier":"allow-confirm","description":"Enables the confirm command without any pre-configured scope.","commands":{"allow":["confirm"],"deny":[]}},"allow-message":{"identifier":"allow-message","description":"Enables the message command without any pre-configured scope.","commands":{"allow":["message"],"deny":[]}},"allow-open":{"identifier":"allow-open","description":"Enables the open command without any pre-configured scope.","commands":{"allow":["open"],"deny":[]}},"allow-save":{"identifier":"allow-save","description":"Enables the save command without any pre-configured scope.","commands":{"allow":["save"],"deny":[]}},"deny-ask":{"identifier":"deny-ask","description":"Denies the ask command without any pre-configured scope.","commands":{"allow":[],"deny":["ask"]}},"deny-confirm":{"identifier":"deny-confirm","description":"Denies the confirm command without any pre-configured scope.","commands":{"allow":[],"deny":["confirm"]}},"deny-message":{"identifier":"deny-message","description":"Denies the message command without any pre-configured scope.","commands":{"allow":[],"deny":["message"]}},"deny-open":{"identifier":"deny-open","description":"Denies the open command without any pre-configured scope.","commands":{"allow":[],"deny":["open"]}},"deny-save":{"identifier":"deny-save","description":"Denies the save command without any pre-configured scope.","commands":{"allow":[],"deny":["save"]}}},"permission_sets":{},"global_scope_schema":null},"opener":{"default_permission":{"identifier":"default","description":"This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer","permissions":["allow-open-url","allow-reveal-item-in-dir","allow-default-urls"]},"permissions":{"allow-default-urls":{"identifier":"allow-default-urls","description":"This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.","commands":{"allow":[],"deny":[]},"scope":{"allow":[{"url":"mailto:*"},{"url":"tel:*"},{"url":"http://*"},{"url":"https://*"}]}},"allow-open-path":{"identifier":"allow-open-path","description":"Enables the open_path command without any pre-configured scope.","commands":{"allow":["open_path"],"deny":[]}},"allow-open-url":{"identifier":"allow-open-url","description":"Enables the open_url command without any pre-configured scope.","commands":{"allow":["open_url"],"deny":[]}},"allow-reveal-item-in-dir":{"identifier":"allow-reveal-item-in-dir","description":"Enables the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":["reveal_item_in_dir"],"deny":[]}},"deny-open-path":{"identifier":"deny-open-path","description":"Denies the open_path command without any pre-configured scope.","commands":{"allow":[],"deny":["open_path"]}},"deny-open-url":{"identifier":"deny-open-url","description":"Denies the open_url command without any pre-configured scope.","commands":{"allow":[],"deny":["open_url"]}},"deny-reveal-item-in-dir":{"identifier":"deny-reveal-item-in-dir","description":"Denies the reveal_item_in_dir command without any pre-configured scope.","commands":{"allow":[],"deny":["reveal_item_in_dir"]}}},"permission_sets":{},"global_scope_schema":{"$schema":"http://json-schema.org/draft-07/schema#","anyOf":[{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this url with, for example: firefox."},"url":{"description":"A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"","type":"string"}},"required":["url"],"type":"object"},{"properties":{"app":{"allOf":[{"$ref":"#/definitions/Application"}],"description":"An application to open this path with, for example: xdg-open."},"path":{"description":"A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.","type":"string"}},"required":["path"],"type":"object"}],"definitions":{"Application":{"anyOf":[{"description":"Open in default application.","type":"null"},{"description":"If true, allow open with any application.","type":"boolean"},{"description":"Allow specific application to open with.","type":"string"}],"description":"Opener scope application."}},"description":"Opener scope entry.","title":"OpenerScopeEntry"}}} \ No newline at end of file diff --git a/packages/tauri-app/src-tauri/gen/schemas/capabilities.json b/packages/tauri-app/src-tauri/gen/schemas/capabilities.json new file mode 100644 index 0000000..c98bf3f --- /dev/null +++ b/packages/tauri-app/src-tauri/gen/schemas/capabilities.json @@ -0,0 +1 @@ +{"main-window-native-dialogs":{"identifier":"main-window-native-dialogs","description":"Grant the main window access to required core features and native dialog commands.","remote":{"urls":["http://127.0.0.1:*","http://localhost:*"]},"local":true,"windows":["main"],"permissions":["core:default","core:menu:default","dialog:allow-open","opener:allow-default-urls","core:webview:allow-set-webview-zoom"]}} \ No newline at end of file diff --git a/packages/tauri-app/src-tauri/gen/schemas/desktop-schema.json b/packages/tauri-app/src-tauri/gen/schemas/desktop-schema.json new file mode 100644 index 0000000..344faff --- /dev/null +++ b/packages/tauri-app/src-tauri/gen/schemas/desktop-schema.json @@ -0,0 +1,2543 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "Application": { + "description": "Opener scope application.", + "anyOf": [ + { + "description": "Open in default application.", + "type": "null" + }, + { + "description": "If true, allow open with any application.", + "type": "boolean" + }, + { + "description": "Allow specific application to open with.", + "type": "string" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/tauri-app/src-tauri/gen/schemas/macOS-schema.json b/packages/tauri-app/src-tauri/gen/schemas/macOS-schema.json new file mode 100644 index 0000000..344faff --- /dev/null +++ b/packages/tauri-app/src-tauri/gen/schemas/macOS-schema.json @@ -0,0 +1,2543 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "CapabilityFile", + "description": "Capability formats accepted in a capability file.", + "anyOf": [ + { + "description": "A single capability.", + "allOf": [ + { + "$ref": "#/definitions/Capability" + } + ] + }, + { + "description": "A list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + }, + { + "description": "A list of capabilities.", + "type": "object", + "required": [ + "capabilities" + ], + "properties": { + "capabilities": { + "description": "The list of capabilities.", + "type": "array", + "items": { + "$ref": "#/definitions/Capability" + } + } + } + } + ], + "definitions": { + "Capability": { + "description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```", + "type": "object", + "required": [ + "identifier", + "permissions" + ], + "properties": { + "identifier": { + "description": "Identifier of the capability.\n\n## Example\n\n`main-user-files-write`", + "type": "string" + }, + "description": { + "description": "Description of what the capability is intended to allow on associated windows.\n\nIt should contain a description of what the grouped permissions should allow.\n\n## Example\n\nThis capability allows the `main` window access to `filesystem` write related commands and `dialog` commands to enable programmatic access to files selected by the user.", + "default": "", + "type": "string" + }, + "remote": { + "description": "Configure remote URLs that can use the capability permissions.\n\nThis setting is optional and defaults to not being set, as our default use case is that the content is served from our local application.\n\n:::caution Make sure you understand the security implications of providing remote sources with local system access. :::\n\n## Example\n\n```json { \"urls\": [\"https://*.mydomain.dev\"] } ```", + "anyOf": [ + { + "$ref": "#/definitions/CapabilityRemote" + }, + { + "type": "null" + } + ] + }, + "local": { + "description": "Whether this capability is enabled for local app URLs or not. Defaults to `true`.", + "default": true, + "type": "boolean" + }, + "windows": { + "description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "webviews": { + "description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`", + "type": "array", + "items": { + "type": "string" + } + }, + "permissions": { + "description": "List of permissions attached to this capability.\n\nMust include the plugin name as prefix in the form of `${plugin-name}:${permission-name}`. For commands directly implemented in the application itself only `${permission-name}` is required.\n\n## Example\n\n```json [ \"core:default\", \"shell:allow-open\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] } ] ```", + "type": "array", + "items": { + "$ref": "#/definitions/PermissionEntry" + }, + "uniqueItems": true + }, + "platforms": { + "description": "Limit which target platforms this capability applies to.\n\nBy default all platforms are targeted.\n\n## Example\n\n`[\"macOS\",\"windows\"]`", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Target" + } + } + } + }, + "CapabilityRemote": { + "description": "Configuration for remote URLs that are associated with the capability.", + "type": "object", + "required": [ + "urls" + ], + "properties": { + "urls": { + "description": "Remote domains this capability refers to using the [URLPattern standard](https://urlpattern.spec.whatwg.org/).\n\n## Examples\n\n- \"https://*.mydomain.dev\": allows subdomains of mydomain.dev - \"https://mydomain.dev/api/*\": allows any subpath of mydomain.dev/api", + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "PermissionEntry": { + "description": "An entry for a permission value in a [`Capability`] can be either a raw permission [`Identifier`] or an object that references a permission and extends its scope.", + "anyOf": [ + { + "description": "Reference a permission or permission set by identifier.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + { + "description": "Reference a permission or permission set by identifier and extends its scope.", + "type": "object", + "allOf": [ + { + "if": { + "properties": { + "identifier": { + "anyOf": [ + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + } + ] + } + } + }, + "then": { + "properties": { + "allow": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + }, + "deny": { + "items": { + "title": "OpenerScopeEntry", + "description": "Opener scope entry.", + "anyOf": [ + { + "type": "object", + "required": [ + "url" + ], + "properties": { + "app": { + "description": "An application to open this url with, for example: firefox.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "url": { + "description": "A URL that can be opened by the webview when using the Opener APIs.\n\nWildcards can be used following the UNIX glob pattern.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"", + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "path" + ], + "properties": { + "app": { + "description": "An application to open this path with, for example: xdg-open.", + "allOf": [ + { + "$ref": "#/definitions/Application" + } + ] + }, + "path": { + "description": "A path that can be opened by the webview when using the Opener APIs.\n\nThe pattern can start with a variable that resolves to a system base directory. The variables are: `$AUDIO`, `$CACHE`, `$CONFIG`, `$DATA`, `$LOCALDATA`, `$DESKTOP`, `$DOCUMENT`, `$DOWNLOAD`, `$EXE`, `$FONT`, `$HOME`, `$PICTURE`, `$PUBLIC`, `$RUNTIME`, `$TEMPLATE`, `$VIDEO`, `$RESOURCE`, `$APP`, `$LOG`, `$TEMP`, `$APPCONFIG`, `$APPDATA`, `$APPLOCALDATA`, `$APPCACHE`, `$APPLOG`.", + "type": "string" + } + } + } + ] + } + } + } + }, + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + } + } + }, + { + "properties": { + "identifier": { + "description": "Identifier of the permission or permission set.", + "allOf": [ + { + "$ref": "#/definitions/Identifier" + } + ] + }, + "allow": { + "description": "Data that defines what is allowed by the scope.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + }, + "deny": { + "description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.", + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Value" + } + } + } + } + ], + "required": [ + "identifier" + ] + } + ] + }, + "Identifier": { + "description": "Permission identifier", + "oneOf": [ + { + "description": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`", + "type": "string", + "const": "core:default", + "markdownDescription": "Default core plugins set.\n#### This default permission set includes:\n\n- `core:path:default`\n- `core:event:default`\n- `core:window:default`\n- `core:webview:default`\n- `core:app:default`\n- `core:image:default`\n- `core:resources:default`\n- `core:menu:default`\n- `core:tray:default`" + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`", + "type": "string", + "const": "core:app:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-version`\n- `allow-name`\n- `allow-tauri-version`\n- `allow-identifier`\n- `allow-bundle-type`\n- `allow-register-listener`\n- `allow-remove-listener`" + }, + { + "description": "Enables the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-hide", + "markdownDescription": "Enables the app_hide command without any pre-configured scope." + }, + { + "description": "Enables the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-app-show", + "markdownDescription": "Enables the app_show command without any pre-configured scope." + }, + { + "description": "Enables the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-bundle-type", + "markdownDescription": "Enables the bundle_type command without any pre-configured scope." + }, + { + "description": "Enables the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-default-window-icon", + "markdownDescription": "Enables the default_window_icon command without any pre-configured scope." + }, + { + "description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-fetch-data-store-identifiers", + "markdownDescription": "Enables the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Enables the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-identifier", + "markdownDescription": "Enables the identifier command without any pre-configured scope." + }, + { + "description": "Enables the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-name", + "markdownDescription": "Enables the name command without any pre-configured scope." + }, + { + "description": "Enables the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-register-listener", + "markdownDescription": "Enables the register_listener command without any pre-configured scope." + }, + { + "description": "Enables the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-data-store", + "markdownDescription": "Enables the remove_data_store command without any pre-configured scope." + }, + { + "description": "Enables the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-remove-listener", + "markdownDescription": "Enables the remove_listener command without any pre-configured scope." + }, + { + "description": "Enables the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-app-theme", + "markdownDescription": "Enables the set_app_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-set-dock-visibility", + "markdownDescription": "Enables the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Enables the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-tauri-version", + "markdownDescription": "Enables the tauri_version command without any pre-configured scope." + }, + { + "description": "Enables the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:allow-version", + "markdownDescription": "Enables the version command without any pre-configured scope." + }, + { + "description": "Denies the app_hide command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-hide", + "markdownDescription": "Denies the app_hide command without any pre-configured scope." + }, + { + "description": "Denies the app_show command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-app-show", + "markdownDescription": "Denies the app_show command without any pre-configured scope." + }, + { + "description": "Denies the bundle_type command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-bundle-type", + "markdownDescription": "Denies the bundle_type command without any pre-configured scope." + }, + { + "description": "Denies the default_window_icon command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-default-window-icon", + "markdownDescription": "Denies the default_window_icon command without any pre-configured scope." + }, + { + "description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-fetch-data-store-identifiers", + "markdownDescription": "Denies the fetch_data_store_identifiers command without any pre-configured scope." + }, + { + "description": "Denies the identifier command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-identifier", + "markdownDescription": "Denies the identifier command without any pre-configured scope." + }, + { + "description": "Denies the name command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-name", + "markdownDescription": "Denies the name command without any pre-configured scope." + }, + { + "description": "Denies the register_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-register-listener", + "markdownDescription": "Denies the register_listener command without any pre-configured scope." + }, + { + "description": "Denies the remove_data_store command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-data-store", + "markdownDescription": "Denies the remove_data_store command without any pre-configured scope." + }, + { + "description": "Denies the remove_listener command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-remove-listener", + "markdownDescription": "Denies the remove_listener command without any pre-configured scope." + }, + { + "description": "Denies the set_app_theme command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-app-theme", + "markdownDescription": "Denies the set_app_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_dock_visibility command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-set-dock-visibility", + "markdownDescription": "Denies the set_dock_visibility command without any pre-configured scope." + }, + { + "description": "Denies the tauri_version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-tauri-version", + "markdownDescription": "Denies the tauri_version command without any pre-configured scope." + }, + { + "description": "Denies the version command without any pre-configured scope.", + "type": "string", + "const": "core:app:deny-version", + "markdownDescription": "Denies the version command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`", + "type": "string", + "const": "core:event:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-listen`\n- `allow-unlisten`\n- `allow-emit`\n- `allow-emit-to`" + }, + { + "description": "Enables the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit", + "markdownDescription": "Enables the emit command without any pre-configured scope." + }, + { + "description": "Enables the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-emit-to", + "markdownDescription": "Enables the emit_to command without any pre-configured scope." + }, + { + "description": "Enables the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-listen", + "markdownDescription": "Enables the listen command without any pre-configured scope." + }, + { + "description": "Enables the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:allow-unlisten", + "markdownDescription": "Enables the unlisten command without any pre-configured scope." + }, + { + "description": "Denies the emit command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit", + "markdownDescription": "Denies the emit command without any pre-configured scope." + }, + { + "description": "Denies the emit_to command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-emit-to", + "markdownDescription": "Denies the emit_to command without any pre-configured scope." + }, + { + "description": "Denies the listen command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-listen", + "markdownDescription": "Denies the listen command without any pre-configured scope." + }, + { + "description": "Denies the unlisten command without any pre-configured scope.", + "type": "string", + "const": "core:event:deny-unlisten", + "markdownDescription": "Denies the unlisten command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`", + "type": "string", + "const": "core:image:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-from-bytes`\n- `allow-from-path`\n- `allow-rgba`\n- `allow-size`" + }, + { + "description": "Enables the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-bytes", + "markdownDescription": "Enables the from_bytes command without any pre-configured scope." + }, + { + "description": "Enables the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-from-path", + "markdownDescription": "Enables the from_path command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-rgba", + "markdownDescription": "Enables the rgba command without any pre-configured scope." + }, + { + "description": "Enables the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:allow-size", + "markdownDescription": "Enables the size command without any pre-configured scope." + }, + { + "description": "Denies the from_bytes command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-bytes", + "markdownDescription": "Denies the from_bytes command without any pre-configured scope." + }, + { + "description": "Denies the from_path command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-from-path", + "markdownDescription": "Denies the from_path command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the rgba command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-rgba", + "markdownDescription": "Denies the rgba command without any pre-configured scope." + }, + { + "description": "Denies the size command without any pre-configured scope.", + "type": "string", + "const": "core:image:deny-size", + "markdownDescription": "Denies the size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`", + "type": "string", + "const": "core:menu:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-append`\n- `allow-prepend`\n- `allow-insert`\n- `allow-remove`\n- `allow-remove-at`\n- `allow-items`\n- `allow-get`\n- `allow-popup`\n- `allow-create-default`\n- `allow-set-as-app-menu`\n- `allow-set-as-window-menu`\n- `allow-text`\n- `allow-set-text`\n- `allow-is-enabled`\n- `allow-set-enabled`\n- `allow-set-accelerator`\n- `allow-set-as-windows-menu-for-nsapp`\n- `allow-set-as-help-menu-for-nsapp`\n- `allow-is-checked`\n- `allow-set-checked`\n- `allow-set-icon`" + }, + { + "description": "Enables the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-append", + "markdownDescription": "Enables the append command without any pre-configured scope." + }, + { + "description": "Enables the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-create-default", + "markdownDescription": "Enables the create_default command without any pre-configured scope." + }, + { + "description": "Enables the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-get", + "markdownDescription": "Enables the get command without any pre-configured scope." + }, + { + "description": "Enables the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-insert", + "markdownDescription": "Enables the insert command without any pre-configured scope." + }, + { + "description": "Enables the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-checked", + "markdownDescription": "Enables the is_checked command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-items", + "markdownDescription": "Enables the items command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-popup", + "markdownDescription": "Enables the popup command without any pre-configured scope." + }, + { + "description": "Enables the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-prepend", + "markdownDescription": "Enables the prepend command without any pre-configured scope." + }, + { + "description": "Enables the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove", + "markdownDescription": "Enables the remove command without any pre-configured scope." + }, + { + "description": "Enables the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-remove-at", + "markdownDescription": "Enables the remove_at command without any pre-configured scope." + }, + { + "description": "Enables the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-accelerator", + "markdownDescription": "Enables the set_accelerator command without any pre-configured scope." + }, + { + "description": "Enables the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-app-menu", + "markdownDescription": "Enables the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-help-menu-for-nsapp", + "markdownDescription": "Enables the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-window-menu", + "markdownDescription": "Enables the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-as-windows-menu-for-nsapp", + "markdownDescription": "Enables the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Enables the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-checked", + "markdownDescription": "Enables the set_checked command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-set-text", + "markdownDescription": "Enables the set_text command without any pre-configured scope." + }, + { + "description": "Enables the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:allow-text", + "markdownDescription": "Enables the text command without any pre-configured scope." + }, + { + "description": "Denies the append command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-append", + "markdownDescription": "Denies the append command without any pre-configured scope." + }, + { + "description": "Denies the create_default command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-create-default", + "markdownDescription": "Denies the create_default command without any pre-configured scope." + }, + { + "description": "Denies the get command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-get", + "markdownDescription": "Denies the get command without any pre-configured scope." + }, + { + "description": "Denies the insert command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-insert", + "markdownDescription": "Denies the insert command without any pre-configured scope." + }, + { + "description": "Denies the is_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-checked", + "markdownDescription": "Denies the is_checked command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the items command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-items", + "markdownDescription": "Denies the items command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the popup command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-popup", + "markdownDescription": "Denies the popup command without any pre-configured scope." + }, + { + "description": "Denies the prepend command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-prepend", + "markdownDescription": "Denies the prepend command without any pre-configured scope." + }, + { + "description": "Denies the remove command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove", + "markdownDescription": "Denies the remove command without any pre-configured scope." + }, + { + "description": "Denies the remove_at command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-remove-at", + "markdownDescription": "Denies the remove_at command without any pre-configured scope." + }, + { + "description": "Denies the set_accelerator command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-accelerator", + "markdownDescription": "Denies the set_accelerator command without any pre-configured scope." + }, + { + "description": "Denies the set_as_app_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-app-menu", + "markdownDescription": "Denies the set_as_app_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-help-menu-for-nsapp", + "markdownDescription": "Denies the set_as_help_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_as_window_menu command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-window-menu", + "markdownDescription": "Denies the set_as_window_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-as-windows-menu-for-nsapp", + "markdownDescription": "Denies the set_as_windows_menu_for_nsapp command without any pre-configured scope." + }, + { + "description": "Denies the set_checked command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-checked", + "markdownDescription": "Denies the set_checked command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-set-text", + "markdownDescription": "Denies the set_text command without any pre-configured scope." + }, + { + "description": "Denies the text command without any pre-configured scope.", + "type": "string", + "const": "core:menu:deny-text", + "markdownDescription": "Denies the text command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`", + "type": "string", + "const": "core:path:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-resolve-directory`\n- `allow-resolve`\n- `allow-normalize`\n- `allow-join`\n- `allow-dirname`\n- `allow-extname`\n- `allow-basename`\n- `allow-is-absolute`" + }, + { + "description": "Enables the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-basename", + "markdownDescription": "Enables the basename command without any pre-configured scope." + }, + { + "description": "Enables the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-dirname", + "markdownDescription": "Enables the dirname command without any pre-configured scope." + }, + { + "description": "Enables the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-extname", + "markdownDescription": "Enables the extname command without any pre-configured scope." + }, + { + "description": "Enables the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-is-absolute", + "markdownDescription": "Enables the is_absolute command without any pre-configured scope." + }, + { + "description": "Enables the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-join", + "markdownDescription": "Enables the join command without any pre-configured scope." + }, + { + "description": "Enables the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-normalize", + "markdownDescription": "Enables the normalize command without any pre-configured scope." + }, + { + "description": "Enables the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve", + "markdownDescription": "Enables the resolve command without any pre-configured scope." + }, + { + "description": "Enables the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:allow-resolve-directory", + "markdownDescription": "Enables the resolve_directory command without any pre-configured scope." + }, + { + "description": "Denies the basename command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-basename", + "markdownDescription": "Denies the basename command without any pre-configured scope." + }, + { + "description": "Denies the dirname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-dirname", + "markdownDescription": "Denies the dirname command without any pre-configured scope." + }, + { + "description": "Denies the extname command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-extname", + "markdownDescription": "Denies the extname command without any pre-configured scope." + }, + { + "description": "Denies the is_absolute command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-is-absolute", + "markdownDescription": "Denies the is_absolute command without any pre-configured scope." + }, + { + "description": "Denies the join command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-join", + "markdownDescription": "Denies the join command without any pre-configured scope." + }, + { + "description": "Denies the normalize command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-normalize", + "markdownDescription": "Denies the normalize command without any pre-configured scope." + }, + { + "description": "Denies the resolve command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve", + "markdownDescription": "Denies the resolve command without any pre-configured scope." + }, + { + "description": "Denies the resolve_directory command without any pre-configured scope.", + "type": "string", + "const": "core:path:deny-resolve-directory", + "markdownDescription": "Denies the resolve_directory command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`", + "type": "string", + "const": "core:resources:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-close`" + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:resources:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`", + "type": "string", + "const": "core:tray:default", + "markdownDescription": "Default permissions for the plugin, which enables all commands.\n#### This default permission set includes:\n\n- `allow-new`\n- `allow-get-by-id`\n- `allow-remove-by-id`\n- `allow-set-icon`\n- `allow-set-menu`\n- `allow-set-tooltip`\n- `allow-set-title`\n- `allow-set-visible`\n- `allow-set-temp-dir-path`\n- `allow-set-icon-as-template`\n- `allow-set-show-menu-on-left-click`" + }, + { + "description": "Enables the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-get-by-id", + "markdownDescription": "Enables the get_by_id command without any pre-configured scope." + }, + { + "description": "Enables the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-new", + "markdownDescription": "Enables the new command without any pre-configured scope." + }, + { + "description": "Enables the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-remove-by-id", + "markdownDescription": "Enables the remove_by_id command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-icon-as-template", + "markdownDescription": "Enables the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Enables the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-menu", + "markdownDescription": "Enables the set_menu command without any pre-configured scope." + }, + { + "description": "Enables the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-show-menu-on-left-click", + "markdownDescription": "Enables the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Enables the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-temp-dir-path", + "markdownDescription": "Enables the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-tooltip", + "markdownDescription": "Enables the set_tooltip command without any pre-configured scope." + }, + { + "description": "Enables the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:allow-set-visible", + "markdownDescription": "Enables the set_visible command without any pre-configured scope." + }, + { + "description": "Denies the get_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-get-by-id", + "markdownDescription": "Denies the get_by_id command without any pre-configured scope." + }, + { + "description": "Denies the new command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-new", + "markdownDescription": "Denies the new command without any pre-configured scope." + }, + { + "description": "Denies the remove_by_id command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-remove-by-id", + "markdownDescription": "Denies the remove_by_id command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_icon_as_template command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-icon-as-template", + "markdownDescription": "Denies the set_icon_as_template command without any pre-configured scope." + }, + { + "description": "Denies the set_menu command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-menu", + "markdownDescription": "Denies the set_menu command without any pre-configured scope." + }, + { + "description": "Denies the set_show_menu_on_left_click command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-show-menu-on-left-click", + "markdownDescription": "Denies the set_show_menu_on_left_click command without any pre-configured scope." + }, + { + "description": "Denies the set_temp_dir_path command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-temp-dir-path", + "markdownDescription": "Denies the set_temp_dir_path command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_tooltip command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-tooltip", + "markdownDescription": "Denies the set_tooltip command without any pre-configured scope." + }, + { + "description": "Denies the set_visible command without any pre-configured scope.", + "type": "string", + "const": "core:tray:deny-set-visible", + "markdownDescription": "Denies the set_visible command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`", + "type": "string", + "const": "core:webview:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-webviews`\n- `allow-webview-position`\n- `allow-webview-size`\n- `allow-internal-toggle-devtools`" + }, + { + "description": "Enables the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-clear-all-browsing-data", + "markdownDescription": "Enables the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Enables the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview", + "markdownDescription": "Enables the create_webview command without any pre-configured scope." + }, + { + "description": "Enables the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-create-webview-window", + "markdownDescription": "Enables the create_webview_window command without any pre-configured scope." + }, + { + "description": "Enables the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-get-all-webviews", + "markdownDescription": "Enables the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-internal-toggle-devtools", + "markdownDescription": "Enables the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Enables the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-print", + "markdownDescription": "Enables the print command without any pre-configured scope." + }, + { + "description": "Enables the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-reparent", + "markdownDescription": "Enables the reparent command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-auto-resize", + "markdownDescription": "Enables the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-background-color", + "markdownDescription": "Enables the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-focus", + "markdownDescription": "Enables the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-position", + "markdownDescription": "Enables the set_webview_position command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-size", + "markdownDescription": "Enables the set_webview_size command without any pre-configured scope." + }, + { + "description": "Enables the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-set-webview-zoom", + "markdownDescription": "Enables the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Enables the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-close", + "markdownDescription": "Enables the webview_close command without any pre-configured scope." + }, + { + "description": "Enables the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-hide", + "markdownDescription": "Enables the webview_hide command without any pre-configured scope." + }, + { + "description": "Enables the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-position", + "markdownDescription": "Enables the webview_position command without any pre-configured scope." + }, + { + "description": "Enables the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-show", + "markdownDescription": "Enables the webview_show command without any pre-configured scope." + }, + { + "description": "Enables the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:allow-webview-size", + "markdownDescription": "Enables the webview_size command without any pre-configured scope." + }, + { + "description": "Denies the clear_all_browsing_data command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-clear-all-browsing-data", + "markdownDescription": "Denies the clear_all_browsing_data command without any pre-configured scope." + }, + { + "description": "Denies the create_webview command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview", + "markdownDescription": "Denies the create_webview command without any pre-configured scope." + }, + { + "description": "Denies the create_webview_window command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-create-webview-window", + "markdownDescription": "Denies the create_webview_window command without any pre-configured scope." + }, + { + "description": "Denies the get_all_webviews command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-get-all-webviews", + "markdownDescription": "Denies the get_all_webviews command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_devtools command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-internal-toggle-devtools", + "markdownDescription": "Denies the internal_toggle_devtools command without any pre-configured scope." + }, + { + "description": "Denies the print command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-print", + "markdownDescription": "Denies the print command without any pre-configured scope." + }, + { + "description": "Denies the reparent command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-reparent", + "markdownDescription": "Denies the reparent command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_auto_resize command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-auto-resize", + "markdownDescription": "Denies the set_webview_auto_resize command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-background-color", + "markdownDescription": "Denies the set_webview_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_focus command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-focus", + "markdownDescription": "Denies the set_webview_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-position", + "markdownDescription": "Denies the set_webview_position command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-size", + "markdownDescription": "Denies the set_webview_size command without any pre-configured scope." + }, + { + "description": "Denies the set_webview_zoom command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-set-webview-zoom", + "markdownDescription": "Denies the set_webview_zoom command without any pre-configured scope." + }, + { + "description": "Denies the webview_close command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-close", + "markdownDescription": "Denies the webview_close command without any pre-configured scope." + }, + { + "description": "Denies the webview_hide command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-hide", + "markdownDescription": "Denies the webview_hide command without any pre-configured scope." + }, + { + "description": "Denies the webview_position command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-position", + "markdownDescription": "Denies the webview_position command without any pre-configured scope." + }, + { + "description": "Denies the webview_show command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-show", + "markdownDescription": "Denies the webview_show command without any pre-configured scope." + }, + { + "description": "Denies the webview_size command without any pre-configured scope.", + "type": "string", + "const": "core:webview:deny-webview-size", + "markdownDescription": "Denies the webview_size command without any pre-configured scope." + }, + { + "description": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`", + "type": "string", + "const": "core:window:default", + "markdownDescription": "Default permissions for the plugin.\n#### This default permission set includes:\n\n- `allow-get-all-windows`\n- `allow-scale-factor`\n- `allow-inner-position`\n- `allow-outer-position`\n- `allow-inner-size`\n- `allow-outer-size`\n- `allow-is-fullscreen`\n- `allow-is-minimized`\n- `allow-is-maximized`\n- `allow-is-focused`\n- `allow-is-decorated`\n- `allow-is-resizable`\n- `allow-is-maximizable`\n- `allow-is-minimizable`\n- `allow-is-closable`\n- `allow-is-visible`\n- `allow-is-enabled`\n- `allow-title`\n- `allow-current-monitor`\n- `allow-primary-monitor`\n- `allow-monitor-from-point`\n- `allow-available-monitors`\n- `allow-cursor-position`\n- `allow-theme`\n- `allow-is-always-on-top`\n- `allow-internal-toggle-maximize`" + }, + { + "description": "Enables the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-available-monitors", + "markdownDescription": "Enables the available_monitors command without any pre-configured scope." + }, + { + "description": "Enables the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-center", + "markdownDescription": "Enables the center command without any pre-configured scope." + }, + { + "description": "Enables the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-close", + "markdownDescription": "Enables the close command without any pre-configured scope." + }, + { + "description": "Enables the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-create", + "markdownDescription": "Enables the create command without any pre-configured scope." + }, + { + "description": "Enables the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-current-monitor", + "markdownDescription": "Enables the current_monitor command without any pre-configured scope." + }, + { + "description": "Enables the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-cursor-position", + "markdownDescription": "Enables the cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-destroy", + "markdownDescription": "Enables the destroy command without any pre-configured scope." + }, + { + "description": "Enables the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-get-all-windows", + "markdownDescription": "Enables the get_all_windows command without any pre-configured scope." + }, + { + "description": "Enables the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-hide", + "markdownDescription": "Enables the hide command without any pre-configured scope." + }, + { + "description": "Enables the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-position", + "markdownDescription": "Enables the inner_position command without any pre-configured scope." + }, + { + "description": "Enables the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-inner-size", + "markdownDescription": "Enables the inner_size command without any pre-configured scope." + }, + { + "description": "Enables the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-internal-toggle-maximize", + "markdownDescription": "Enables the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-always-on-top", + "markdownDescription": "Enables the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-closable", + "markdownDescription": "Enables the is_closable command without any pre-configured scope." + }, + { + "description": "Enables the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-decorated", + "markdownDescription": "Enables the is_decorated command without any pre-configured scope." + }, + { + "description": "Enables the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-enabled", + "markdownDescription": "Enables the is_enabled command without any pre-configured scope." + }, + { + "description": "Enables the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-focused", + "markdownDescription": "Enables the is_focused command without any pre-configured scope." + }, + { + "description": "Enables the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-fullscreen", + "markdownDescription": "Enables the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximizable", + "markdownDescription": "Enables the is_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-maximized", + "markdownDescription": "Enables the is_maximized command without any pre-configured scope." + }, + { + "description": "Enables the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimizable", + "markdownDescription": "Enables the is_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-minimized", + "markdownDescription": "Enables the is_minimized command without any pre-configured scope." + }, + { + "description": "Enables the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-resizable", + "markdownDescription": "Enables the is_resizable command without any pre-configured scope." + }, + { + "description": "Enables the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-is-visible", + "markdownDescription": "Enables the is_visible command without any pre-configured scope." + }, + { + "description": "Enables the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-maximize", + "markdownDescription": "Enables the maximize command without any pre-configured scope." + }, + { + "description": "Enables the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-minimize", + "markdownDescription": "Enables the minimize command without any pre-configured scope." + }, + { + "description": "Enables the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-monitor-from-point", + "markdownDescription": "Enables the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Enables the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-position", + "markdownDescription": "Enables the outer_position command without any pre-configured scope." + }, + { + "description": "Enables the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-outer-size", + "markdownDescription": "Enables the outer_size command without any pre-configured scope." + }, + { + "description": "Enables the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-primary-monitor", + "markdownDescription": "Enables the primary_monitor command without any pre-configured scope." + }, + { + "description": "Enables the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-request-user-attention", + "markdownDescription": "Enables the request_user_attention command without any pre-configured scope." + }, + { + "description": "Enables the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-scale-factor", + "markdownDescription": "Enables the scale_factor command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-bottom", + "markdownDescription": "Enables the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Enables the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-always-on-top", + "markdownDescription": "Enables the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Enables the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-background-color", + "markdownDescription": "Enables the set_background_color command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-count", + "markdownDescription": "Enables the set_badge_count command without any pre-configured scope." + }, + { + "description": "Enables the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-badge-label", + "markdownDescription": "Enables the set_badge_label command without any pre-configured scope." + }, + { + "description": "Enables the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-closable", + "markdownDescription": "Enables the set_closable command without any pre-configured scope." + }, + { + "description": "Enables the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-content-protected", + "markdownDescription": "Enables the set_content_protected command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-grab", + "markdownDescription": "Enables the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-icon", + "markdownDescription": "Enables the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-position", + "markdownDescription": "Enables the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Enables the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-cursor-visible", + "markdownDescription": "Enables the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Enables the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-decorations", + "markdownDescription": "Enables the set_decorations command without any pre-configured scope." + }, + { + "description": "Enables the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-effects", + "markdownDescription": "Enables the set_effects command without any pre-configured scope." + }, + { + "description": "Enables the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-enabled", + "markdownDescription": "Enables the set_enabled command without any pre-configured scope." + }, + { + "description": "Enables the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focus", + "markdownDescription": "Enables the set_focus command without any pre-configured scope." + }, + { + "description": "Enables the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-focusable", + "markdownDescription": "Enables the set_focusable command without any pre-configured scope." + }, + { + "description": "Enables the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-fullscreen", + "markdownDescription": "Enables the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-icon", + "markdownDescription": "Enables the set_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-ignore-cursor-events", + "markdownDescription": "Enables the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Enables the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-max-size", + "markdownDescription": "Enables the set_max_size command without any pre-configured scope." + }, + { + "description": "Enables the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-maximizable", + "markdownDescription": "Enables the set_maximizable command without any pre-configured scope." + }, + { + "description": "Enables the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-min-size", + "markdownDescription": "Enables the set_min_size command without any pre-configured scope." + }, + { + "description": "Enables the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-minimizable", + "markdownDescription": "Enables the set_minimizable command without any pre-configured scope." + }, + { + "description": "Enables the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-overlay-icon", + "markdownDescription": "Enables the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Enables the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-position", + "markdownDescription": "Enables the set_position command without any pre-configured scope." + }, + { + "description": "Enables the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-progress-bar", + "markdownDescription": "Enables the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Enables the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-resizable", + "markdownDescription": "Enables the set_resizable command without any pre-configured scope." + }, + { + "description": "Enables the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-shadow", + "markdownDescription": "Enables the set_shadow command without any pre-configured scope." + }, + { + "description": "Enables the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-simple-fullscreen", + "markdownDescription": "Enables the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Enables the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size", + "markdownDescription": "Enables the set_size command without any pre-configured scope." + }, + { + "description": "Enables the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-size-constraints", + "markdownDescription": "Enables the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Enables the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-skip-taskbar", + "markdownDescription": "Enables the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Enables the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-theme", + "markdownDescription": "Enables the set_theme command without any pre-configured scope." + }, + { + "description": "Enables the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title", + "markdownDescription": "Enables the set_title command without any pre-configured scope." + }, + { + "description": "Enables the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-title-bar-style", + "markdownDescription": "Enables the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Enables the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-set-visible-on-all-workspaces", + "markdownDescription": "Enables the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Enables the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-show", + "markdownDescription": "Enables the show command without any pre-configured scope." + }, + { + "description": "Enables the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-dragging", + "markdownDescription": "Enables the start_dragging command without any pre-configured scope." + }, + { + "description": "Enables the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-start-resize-dragging", + "markdownDescription": "Enables the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Enables the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-theme", + "markdownDescription": "Enables the theme command without any pre-configured scope." + }, + { + "description": "Enables the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-title", + "markdownDescription": "Enables the title command without any pre-configured scope." + }, + { + "description": "Enables the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-toggle-maximize", + "markdownDescription": "Enables the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Enables the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unmaximize", + "markdownDescription": "Enables the unmaximize command without any pre-configured scope." + }, + { + "description": "Enables the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:allow-unminimize", + "markdownDescription": "Enables the unminimize command without any pre-configured scope." + }, + { + "description": "Denies the available_monitors command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-available-monitors", + "markdownDescription": "Denies the available_monitors command without any pre-configured scope." + }, + { + "description": "Denies the center command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-center", + "markdownDescription": "Denies the center command without any pre-configured scope." + }, + { + "description": "Denies the close command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-close", + "markdownDescription": "Denies the close command without any pre-configured scope." + }, + { + "description": "Denies the create command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-create", + "markdownDescription": "Denies the create command without any pre-configured scope." + }, + { + "description": "Denies the current_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-current-monitor", + "markdownDescription": "Denies the current_monitor command without any pre-configured scope." + }, + { + "description": "Denies the cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-cursor-position", + "markdownDescription": "Denies the cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the destroy command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-destroy", + "markdownDescription": "Denies the destroy command without any pre-configured scope." + }, + { + "description": "Denies the get_all_windows command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-get-all-windows", + "markdownDescription": "Denies the get_all_windows command without any pre-configured scope." + }, + { + "description": "Denies the hide command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-hide", + "markdownDescription": "Denies the hide command without any pre-configured scope." + }, + { + "description": "Denies the inner_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-position", + "markdownDescription": "Denies the inner_position command without any pre-configured scope." + }, + { + "description": "Denies the inner_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-inner-size", + "markdownDescription": "Denies the inner_size command without any pre-configured scope." + }, + { + "description": "Denies the internal_toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-internal-toggle-maximize", + "markdownDescription": "Denies the internal_toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the is_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-always-on-top", + "markdownDescription": "Denies the is_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the is_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-closable", + "markdownDescription": "Denies the is_closable command without any pre-configured scope." + }, + { + "description": "Denies the is_decorated command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-decorated", + "markdownDescription": "Denies the is_decorated command without any pre-configured scope." + }, + { + "description": "Denies the is_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-enabled", + "markdownDescription": "Denies the is_enabled command without any pre-configured scope." + }, + { + "description": "Denies the is_focused command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-focused", + "markdownDescription": "Denies the is_focused command without any pre-configured scope." + }, + { + "description": "Denies the is_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-fullscreen", + "markdownDescription": "Denies the is_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the is_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximizable", + "markdownDescription": "Denies the is_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the is_maximized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-maximized", + "markdownDescription": "Denies the is_maximized command without any pre-configured scope." + }, + { + "description": "Denies the is_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimizable", + "markdownDescription": "Denies the is_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the is_minimized command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-minimized", + "markdownDescription": "Denies the is_minimized command without any pre-configured scope." + }, + { + "description": "Denies the is_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-resizable", + "markdownDescription": "Denies the is_resizable command without any pre-configured scope." + }, + { + "description": "Denies the is_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-is-visible", + "markdownDescription": "Denies the is_visible command without any pre-configured scope." + }, + { + "description": "Denies the maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-maximize", + "markdownDescription": "Denies the maximize command without any pre-configured scope." + }, + { + "description": "Denies the minimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-minimize", + "markdownDescription": "Denies the minimize command without any pre-configured scope." + }, + { + "description": "Denies the monitor_from_point command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-monitor-from-point", + "markdownDescription": "Denies the monitor_from_point command without any pre-configured scope." + }, + { + "description": "Denies the outer_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-position", + "markdownDescription": "Denies the outer_position command without any pre-configured scope." + }, + { + "description": "Denies the outer_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-outer-size", + "markdownDescription": "Denies the outer_size command without any pre-configured scope." + }, + { + "description": "Denies the primary_monitor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-primary-monitor", + "markdownDescription": "Denies the primary_monitor command without any pre-configured scope." + }, + { + "description": "Denies the request_user_attention command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-request-user-attention", + "markdownDescription": "Denies the request_user_attention command without any pre-configured scope." + }, + { + "description": "Denies the scale_factor command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-scale-factor", + "markdownDescription": "Denies the scale_factor command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_bottom command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-bottom", + "markdownDescription": "Denies the set_always_on_bottom command without any pre-configured scope." + }, + { + "description": "Denies the set_always_on_top command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-always-on-top", + "markdownDescription": "Denies the set_always_on_top command without any pre-configured scope." + }, + { + "description": "Denies the set_background_color command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-background-color", + "markdownDescription": "Denies the set_background_color command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_count command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-count", + "markdownDescription": "Denies the set_badge_count command without any pre-configured scope." + }, + { + "description": "Denies the set_badge_label command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-badge-label", + "markdownDescription": "Denies the set_badge_label command without any pre-configured scope." + }, + { + "description": "Denies the set_closable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-closable", + "markdownDescription": "Denies the set_closable command without any pre-configured scope." + }, + { + "description": "Denies the set_content_protected command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-content-protected", + "markdownDescription": "Denies the set_content_protected command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_grab command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-grab", + "markdownDescription": "Denies the set_cursor_grab command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-icon", + "markdownDescription": "Denies the set_cursor_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-position", + "markdownDescription": "Denies the set_cursor_position command without any pre-configured scope." + }, + { + "description": "Denies the set_cursor_visible command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-cursor-visible", + "markdownDescription": "Denies the set_cursor_visible command without any pre-configured scope." + }, + { + "description": "Denies the set_decorations command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-decorations", + "markdownDescription": "Denies the set_decorations command without any pre-configured scope." + }, + { + "description": "Denies the set_effects command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-effects", + "markdownDescription": "Denies the set_effects command without any pre-configured scope." + }, + { + "description": "Denies the set_enabled command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-enabled", + "markdownDescription": "Denies the set_enabled command without any pre-configured scope." + }, + { + "description": "Denies the set_focus command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focus", + "markdownDescription": "Denies the set_focus command without any pre-configured scope." + }, + { + "description": "Denies the set_focusable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-focusable", + "markdownDescription": "Denies the set_focusable command without any pre-configured scope." + }, + { + "description": "Denies the set_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-fullscreen", + "markdownDescription": "Denies the set_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-icon", + "markdownDescription": "Denies the set_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_ignore_cursor_events command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-ignore-cursor-events", + "markdownDescription": "Denies the set_ignore_cursor_events command without any pre-configured scope." + }, + { + "description": "Denies the set_max_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-max-size", + "markdownDescription": "Denies the set_max_size command without any pre-configured scope." + }, + { + "description": "Denies the set_maximizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-maximizable", + "markdownDescription": "Denies the set_maximizable command without any pre-configured scope." + }, + { + "description": "Denies the set_min_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-min-size", + "markdownDescription": "Denies the set_min_size command without any pre-configured scope." + }, + { + "description": "Denies the set_minimizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-minimizable", + "markdownDescription": "Denies the set_minimizable command without any pre-configured scope." + }, + { + "description": "Denies the set_overlay_icon command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-overlay-icon", + "markdownDescription": "Denies the set_overlay_icon command without any pre-configured scope." + }, + { + "description": "Denies the set_position command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-position", + "markdownDescription": "Denies the set_position command without any pre-configured scope." + }, + { + "description": "Denies the set_progress_bar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-progress-bar", + "markdownDescription": "Denies the set_progress_bar command without any pre-configured scope." + }, + { + "description": "Denies the set_resizable command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-resizable", + "markdownDescription": "Denies the set_resizable command without any pre-configured scope." + }, + { + "description": "Denies the set_shadow command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-shadow", + "markdownDescription": "Denies the set_shadow command without any pre-configured scope." + }, + { + "description": "Denies the set_simple_fullscreen command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-simple-fullscreen", + "markdownDescription": "Denies the set_simple_fullscreen command without any pre-configured scope." + }, + { + "description": "Denies the set_size command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size", + "markdownDescription": "Denies the set_size command without any pre-configured scope." + }, + { + "description": "Denies the set_size_constraints command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-size-constraints", + "markdownDescription": "Denies the set_size_constraints command without any pre-configured scope." + }, + { + "description": "Denies the set_skip_taskbar command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-skip-taskbar", + "markdownDescription": "Denies the set_skip_taskbar command without any pre-configured scope." + }, + { + "description": "Denies the set_theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-theme", + "markdownDescription": "Denies the set_theme command without any pre-configured scope." + }, + { + "description": "Denies the set_title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title", + "markdownDescription": "Denies the set_title command without any pre-configured scope." + }, + { + "description": "Denies the set_title_bar_style command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-title-bar-style", + "markdownDescription": "Denies the set_title_bar_style command without any pre-configured scope." + }, + { + "description": "Denies the set_visible_on_all_workspaces command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-set-visible-on-all-workspaces", + "markdownDescription": "Denies the set_visible_on_all_workspaces command without any pre-configured scope." + }, + { + "description": "Denies the show command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-show", + "markdownDescription": "Denies the show command without any pre-configured scope." + }, + { + "description": "Denies the start_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-dragging", + "markdownDescription": "Denies the start_dragging command without any pre-configured scope." + }, + { + "description": "Denies the start_resize_dragging command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-start-resize-dragging", + "markdownDescription": "Denies the start_resize_dragging command without any pre-configured scope." + }, + { + "description": "Denies the theme command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-theme", + "markdownDescription": "Denies the theme command without any pre-configured scope." + }, + { + "description": "Denies the title command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-title", + "markdownDescription": "Denies the title command without any pre-configured scope." + }, + { + "description": "Denies the toggle_maximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-toggle-maximize", + "markdownDescription": "Denies the toggle_maximize command without any pre-configured scope." + }, + { + "description": "Denies the unmaximize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unmaximize", + "markdownDescription": "Denies the unmaximize command without any pre-configured scope." + }, + { + "description": "Denies the unminimize command without any pre-configured scope.", + "type": "string", + "const": "core:window:deny-unminimize", + "markdownDescription": "Denies the unminimize command without any pre-configured scope." + }, + { + "description": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`", + "type": "string", + "const": "dialog:default", + "markdownDescription": "This permission set configures the types of dialogs\navailable from the dialog plugin.\n\n#### Granted Permissions\n\nAll dialog types are enabled.\n\n\n\n#### This default permission set includes:\n\n- `allow-ask`\n- `allow-confirm`\n- `allow-message`\n- `allow-save`\n- `allow-open`" + }, + { + "description": "Enables the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-ask", + "markdownDescription": "Enables the ask command without any pre-configured scope." + }, + { + "description": "Enables the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-confirm", + "markdownDescription": "Enables the confirm command without any pre-configured scope." + }, + { + "description": "Enables the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-message", + "markdownDescription": "Enables the message command without any pre-configured scope." + }, + { + "description": "Enables the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-open", + "markdownDescription": "Enables the open command without any pre-configured scope." + }, + { + "description": "Enables the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:allow-save", + "markdownDescription": "Enables the save command without any pre-configured scope." + }, + { + "description": "Denies the ask command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-ask", + "markdownDescription": "Denies the ask command without any pre-configured scope." + }, + { + "description": "Denies the confirm command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-confirm", + "markdownDescription": "Denies the confirm command without any pre-configured scope." + }, + { + "description": "Denies the message command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-message", + "markdownDescription": "Denies the message command without any pre-configured scope." + }, + { + "description": "Denies the open command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-open", + "markdownDescription": "Denies the open command without any pre-configured scope." + }, + { + "description": "Denies the save command without any pre-configured scope.", + "type": "string", + "const": "dialog:deny-save", + "markdownDescription": "Denies the save command without any pre-configured scope." + }, + { + "description": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`", + "type": "string", + "const": "opener:default", + "markdownDescription": "This permission set allows opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application\nas well as reveal file in directories using default file explorer\n#### This default permission set includes:\n\n- `allow-open-url`\n- `allow-reveal-item-in-dir`\n- `allow-default-urls`" + }, + { + "description": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application.", + "type": "string", + "const": "opener:allow-default-urls", + "markdownDescription": "This enables opening `mailto:`, `tel:`, `https://` and `http://` urls using their default application." + }, + { + "description": "Enables the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-path", + "markdownDescription": "Enables the open_path command without any pre-configured scope." + }, + { + "description": "Enables the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-open-url", + "markdownDescription": "Enables the open_url command without any pre-configured scope." + }, + { + "description": "Enables the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:allow-reveal-item-in-dir", + "markdownDescription": "Enables the reveal_item_in_dir command without any pre-configured scope." + }, + { + "description": "Denies the open_path command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-path", + "markdownDescription": "Denies the open_path command without any pre-configured scope." + }, + { + "description": "Denies the open_url command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-open-url", + "markdownDescription": "Denies the open_url command without any pre-configured scope." + }, + { + "description": "Denies the reveal_item_in_dir command without any pre-configured scope.", + "type": "string", + "const": "opener:deny-reveal-item-in-dir", + "markdownDescription": "Denies the reveal_item_in_dir command without any pre-configured scope." + } + ] + }, + "Value": { + "description": "All supported ACL values.", + "anyOf": [ + { + "description": "Represents a null JSON value.", + "type": "null" + }, + { + "description": "Represents a [`bool`].", + "type": "boolean" + }, + { + "description": "Represents a valid ACL [`Number`].", + "allOf": [ + { + "$ref": "#/definitions/Number" + } + ] + }, + { + "description": "Represents a [`String`].", + "type": "string" + }, + { + "description": "Represents a list of other [`Value`]s.", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + { + "description": "Represents a map of [`String`] keys to [`Value`]s.", + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Value" + } + } + ] + }, + "Number": { + "description": "A valid ACL number.", + "anyOf": [ + { + "description": "Represents an [`i64`].", + "type": "integer", + "format": "int64" + }, + { + "description": "Represents a [`f64`].", + "type": "number", + "format": "double" + } + ] + }, + "Target": { + "description": "Platform target.", + "oneOf": [ + { + "description": "MacOS.", + "type": "string", + "enum": [ + "macOS" + ] + }, + { + "description": "Windows.", + "type": "string", + "enum": [ + "windows" + ] + }, + { + "description": "Linux.", + "type": "string", + "enum": [ + "linux" + ] + }, + { + "description": "Android.", + "type": "string", + "enum": [ + "android" + ] + }, + { + "description": "iOS.", + "type": "string", + "enum": [ + "iOS" + ] + } + ] + }, + "Application": { + "description": "Opener scope application.", + "anyOf": [ + { + "description": "Open in default application.", + "type": "null" + }, + { + "description": "If true, allow open with any application.", + "type": "boolean" + }, + { + "description": "Allow specific application to open with.", + "type": "string" + } + ] + } + } +} \ No newline at end of file diff --git a/packages/tauri-app/src-tauri/icon.icns b/packages/tauri-app/src-tauri/icon.icns new file mode 100644 index 0000000..4431bb4 Binary files /dev/null and b/packages/tauri-app/src-tauri/icon.icns differ diff --git a/packages/tauri-app/src-tauri/icon.ico b/packages/tauri-app/src-tauri/icon.ico new file mode 100644 index 0000000..7d50243 Binary files /dev/null and b/packages/tauri-app/src-tauri/icon.ico differ diff --git a/packages/tauri-app/src-tauri/icon.png b/packages/tauri-app/src-tauri/icon.png new file mode 100644 index 0000000..4c08915 Binary files /dev/null and b/packages/tauri-app/src-tauri/icon.png differ diff --git a/packages/tauri-app/src-tauri/src/cli_manager.rs b/packages/tauri-app/src-tauri/src/cli_manager.rs new file mode 100644 index 0000000..6b43574 --- /dev/null +++ b/packages/tauri-app/src-tauri/src/cli_manager.rs @@ -0,0 +1,712 @@ +use dirs::home_dir; +use parking_lot::Mutex; +use regex::Regex; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::collections::VecDeque; +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::io::{BufRead, BufReader}; +use std::path::PathBuf; +use std::process::{Child, Command, Stdio}; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::{Duration, Instant}; +use tauri::{AppHandle, Emitter, Manager, Url}; + +fn log_line(message: &str) { + println!("[tauri-cli] {message}"); +} + +fn workspace_root() -> Option { + std::env::current_dir().ok().and_then(|mut dir| { + for _ in 0..3 { + if let Some(parent) = dir.parent() { + dir = parent.to_path_buf(); + } + } + Some(dir) + }) +} + +fn navigate_main(app: &AppHandle, url: &str) { + if let Some(win) = app.webview_windows().get("main") { + log_line(&format!("navigating main to {url}")); + if let Ok(parsed) = Url::parse(url) { + let _ = win.navigate(parsed); + } else { + log_line("failed to parse URL for navigation"); + } + } else { + log_line("main window not found for navigation"); + } +} + +const DEFAULT_CONFIG_PATH: &str = "~/.config/codenomad/config.json"; + +#[derive(Debug, Deserialize)] +struct PreferencesConfig { + #[serde(rename = "listeningMode")] + listening_mode: Option, +} + +#[derive(Debug, Deserialize)] +struct AppConfig { + preferences: Option, +} + +fn resolve_config_path() -> PathBuf { + let raw = env::var("CLI_CONFIG") + .ok() + .filter(|value| !value.trim().is_empty()) + .unwrap_or_else(|| DEFAULT_CONFIG_PATH.to_string()); + expand_home(&raw) +} + +fn expand_home(path: &str) -> PathBuf { + if path.starts_with("~/") { + if let Some(home) = home_dir().or_else(|| env::var("HOME").ok().map(PathBuf::from)) { + return home.join(path.trim_start_matches("~/")); + } + } + PathBuf::from(path) +} + +fn resolve_listening_mode() -> String { + let path = resolve_config_path(); + if let Ok(content) = fs::read_to_string(path) { + if let Ok(config) = serde_json::from_str::(&content) { + if let Some(mode) = config + .preferences + .as_ref() + .and_then(|prefs| prefs.listening_mode.as_ref()) + { + if mode == "local" { + return "local".to_string(); + } + if mode == "all" { + return "all".to_string(); + } + } + } + } + "local".to_string() +} + +fn resolve_listening_host() -> String { + let mode = resolve_listening_mode(); + if mode == "local" { + "127.0.0.1".to_string() + } else { + "0.0.0.0".to_string() + } +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum CliState { + Starting, + Ready, + Error, + Stopped, +} + +#[derive(Debug, Clone, Serialize)] +pub struct CliStatus { + pub state: CliState, + pub pid: Option, + pub port: Option, + pub url: Option, + pub error: Option, +} + +impl Default for CliStatus { + fn default() -> Self { + Self { + state: CliState::Stopped, + pid: None, + port: None, + url: None, + error: None, + } + } +} + +#[derive(Debug, Clone)] +pub struct CliProcessManager { + status: Arc>, + child: Arc>>, + ready: Arc, +} + +impl CliProcessManager { + pub fn new() -> Self { + Self { + status: Arc::new(Mutex::new(CliStatus::default())), + child: Arc::new(Mutex::new(None)), + ready: Arc::new(AtomicBool::new(false)), + } + } + + pub fn start(&self, app: AppHandle, dev: bool) -> anyhow::Result<()> { + log_line(&format!("start requested (dev={dev})")); + self.stop()?; + self.ready.store(false, Ordering::SeqCst); + { + let mut status = self.status.lock(); + status.state = CliState::Starting; + status.port = None; + status.url = None; + status.error = None; + status.pid = None; + } + Self::emit_status(&app, &self.status.lock()); + + let status_arc = self.status.clone(); + let child_arc = self.child.clone(); + let ready_flag = self.ready.clone(); + thread::spawn(move || { + if let Err(err) = Self::spawn_cli(app.clone(), status_arc.clone(), child_arc, ready_flag, dev) { + log_line(&format!("cli spawn failed: {err}")); + let mut locked = status_arc.lock(); + locked.state = CliState::Error; + locked.error = Some(err.to_string()); + let snapshot = locked.clone(); + drop(locked); + let _ = app.emit("cli:error", json!({"message": err.to_string()})); + let _ = app.emit("cli:status", snapshot); + } + }); + + Ok(()) + } + + pub fn stop(&self) -> anyhow::Result<()> { + let mut child_opt = self.child.lock(); + if let Some(mut child) = child_opt.take() { + #[cfg(unix)] + unsafe { + libc::kill(child.id() as i32, libc::SIGTERM); + } + #[cfg(windows)] + { + let _ = child.kill(); + } + + let start = Instant::now(); + loop { + match child.try_wait() { + Ok(Some(_)) => break, + Ok(None) => { + if start.elapsed() > Duration::from_secs(4) { + #[cfg(unix)] + unsafe { + libc::kill(child.id() as i32, libc::SIGKILL); + } + #[cfg(windows)] + { + let _ = child.kill(); + } + break; + } + thread::sleep(Duration::from_millis(50)); + } + Err(_) => break, + } + } + } + + let mut status = self.status.lock(); + status.state = CliState::Stopped; + status.pid = None; + status.port = None; + status.url = None; + status.error = None; + + Ok(()) + } + + pub fn status(&self) -> CliStatus { + self.status.lock().clone() + } + + fn spawn_cli( + app: AppHandle, + status: Arc>, + child_holder: Arc>>, + ready: Arc, + dev: bool, + ) -> anyhow::Result<()> { + log_line("resolving CLI entry"); + let resolution = CliEntry::resolve(&app, dev)?; + let host = resolve_listening_host(); + log_line(&format!( + "resolved CLI entry runner={:?} entry={} host={}", + resolution.runner, resolution.entry, host + )); + let args = resolution.build_args(dev, &host); + log_line(&format!("CLI args: {:?}", args)); + if dev { + log_line("development mode: will prefer tsx + source if present"); + } + + let cwd = workspace_root(); + if let Some(ref c) = cwd { + log_line(&format!("using cwd={}", c.display())); + } + + let command_info = if supports_user_shell() { + log_line("spawning via user shell"); + ShellCommandType::UserShell(build_shell_command_string(&resolution, &args)?) + } else { + log_line("spawning directly with node"); + ShellCommandType::Direct(DirectCommand { + program: resolution.node_binary.clone(), + args: resolution.runner_args(&args), + }) + }; + + if !supports_user_shell() { + if which::which(&resolution.node_binary).is_err() { + return Err(anyhow::anyhow!("Node binary not found. Make sure Node.js is installed.")); + } + } + + let child = match &command_info { + ShellCommandType::UserShell(cmd) => { + log_line(&format!("spawn command: {} {:?}", cmd.shell, cmd.args)); + let mut c = Command::new(&cmd.shell); + c.args(&cmd.args) + .env("ELECTRON_RUN_AS_NODE", "1") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if let Some(ref cwd) = cwd { + c.current_dir(cwd); + } + c.spawn()? + } + ShellCommandType::Direct(cmd) => { + log_line(&format!("spawn command: {} {:?}", cmd.program, cmd.args)); + let mut c = Command::new(&cmd.program); + c.args(&cmd.args) + .env("ELECTRON_RUN_AS_NODE", "1") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()); + if let Some(ref cwd) = cwd { + c.current_dir(cwd); + } + c.spawn()? + } + }; + + let pid = child.id(); + log_line(&format!("spawned pid={pid}")); + { + let mut locked = status.lock(); + locked.pid = Some(pid); + } + Self::emit_status(&app, &status.lock()); + + { + let mut holder = child_holder.lock(); + *holder = Some(child); + } + + let child_clone = child_holder.clone(); + let status_clone = status.clone(); + let app_clone = app.clone(); + let ready_clone = ready.clone(); + + thread::spawn(move || { + let stdout = child_clone + .lock() + .as_mut() + .and_then(|c| c.stdout.take()) + .map(BufReader::new); + let stderr = child_clone + .lock() + .as_mut() + .and_then(|c| c.stderr.take()) + .map(BufReader::new); + + if let Some(reader) = stdout { + Self::process_stream(reader, "stdout", &app_clone, &status_clone, &ready_clone); + } + if let Some(reader) = stderr { + Self::process_stream(reader, "stderr", &app_clone, &status_clone, &ready_clone); + } + }); + + let app_clone = app.clone(); + let status_clone = status.clone(); + let ready_clone = ready.clone(); + let child_holder_clone = child_holder.clone(); + thread::spawn(move || { + let timeout = Duration::from_secs(60); + thread::sleep(timeout); + if ready_clone.load(Ordering::SeqCst) { + return; + } + let mut locked = status_clone.lock(); + locked.state = CliState::Error; + locked.error = Some("CLI did not start in time".to_string()); + log_line("timeout waiting for CLI readiness"); + if let Some(child) = child_holder_clone.lock().as_mut() { + let _ = child.kill(); + } + let _ = app_clone.emit("cli:error", json!({"message": "CLI did not start in time"})); + Self::emit_status(&app_clone, &locked); + }); + + let status_clone = status.clone(); + let app_clone = app.clone(); + thread::spawn(move || { + let code = { + let mut guard = child_holder.lock(); + if let Some(child) = guard.as_mut() { + child.wait().ok() + } else { + None + } + }; + + let mut locked = status_clone.lock(); + let failed = locked.state != CliState::Ready; + let err_msg = if failed { + Some(match code { + Some(status) => format!("CLI exited early: {status}"), + None => "CLI exited early".to_string(), + }) + } else { + None + }; + + if failed { + locked.state = CliState::Error; + if locked.error.is_none() { + locked.error = err_msg.clone(); + } + log_line(&format!("cli process exited before ready: {:?}", locked.error)); + let _ = app_clone.emit("cli:error", json!({"message": locked.error.clone().unwrap_or_default()})); + } else { + locked.state = CliState::Stopped; + log_line("cli process stopped cleanly"); + } + + Self::emit_status(&app_clone, &locked); + }); + + Ok(()) + } + + fn process_stream( + mut reader: R, + stream: &str, + app: &AppHandle, + status: &Arc>, + ready: &Arc, + ) { + let mut buffer = String::new(); + let port_regex = Regex::new(r"CodeNomad Server is ready at http://[^:]+:(\d+)").ok(); + let http_regex = Regex::new(r":(\d{2,5})(?!.*:\d)").ok(); + + loop { + buffer.clear(); + match reader.read_line(&mut buffer) { + Ok(0) => break, + Ok(_) => { + let line = buffer.trim_end(); + if !line.is_empty() { + log_line(&format!("[cli][{}] {}", stream, line)); + + if ready.load(Ordering::SeqCst) { + continue; + } + + if let Some(port) = port_regex + .as_ref() + .and_then(|re| re.captures(line).and_then(|c| c.get(1))) + .and_then(|m| m.as_str().parse::().ok()) + { + Self::mark_ready(app, status, ready, port); + continue; + } + + if line.to_lowercase().contains("http server listening") { + if let Some(port) = http_regex + .as_ref() + .and_then(|re| re.captures(line).and_then(|c| c.get(1))) + .and_then(|m| m.as_str().parse::().ok()) + { + Self::mark_ready(app, status, ready, port); + continue; + } + + if let Ok(value) = serde_json::from_str::(line) { + if let Some(port) = value.get("port").and_then(|p| p.as_u64()) { + Self::mark_ready(app, status, ready, port as u16); + continue; + } + } + } + } + } + Err(_) => break, + } + } + } + + fn mark_ready(app: &AppHandle, status: &Arc>, ready: &Arc, port: u16) { + ready.store(true, Ordering::SeqCst); + let mut locked = status.lock(); + let url = format!("http://127.0.0.1:{port}"); + locked.port = Some(port); + locked.url = Some(url.clone()); + locked.state = CliState::Ready; + locked.error = None; + log_line(&format!("cli ready on {url}")); + navigate_main(app, &url); + let _ = app.emit("cli:ready", locked.clone()); + Self::emit_status(app, &locked); + } + + fn emit_status(app: &AppHandle, status: &CliStatus) { + let _ = app.emit("cli:status", status.clone()); + } +} + +fn supports_user_shell() -> bool { + cfg!(unix) +} + +#[derive(Debug)] +struct ShellCommand { + shell: String, + args: Vec, +} + +#[derive(Debug)] +struct DirectCommand { + program: String, + args: Vec, +} + +#[derive(Debug)] +enum ShellCommandType { + UserShell(ShellCommand), + Direct(DirectCommand), +} + +#[derive(Debug)] +struct CliEntry { + entry: String, + runner: Runner, + runner_path: Option, + node_binary: String, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Runner { + Node, + Tsx, +} + +impl CliEntry { + fn resolve(app: &AppHandle, dev: bool) -> anyhow::Result { + let node_binary = std::env::var("NODE_BINARY").unwrap_or_else(|_| "node".to_string()); + + if dev { + if let Some(tsx_path) = resolve_tsx(app) { + if let Some(entry) = resolve_dev_entry(app) { + return Ok(Self { + entry, + runner: Runner::Tsx, + runner_path: Some(tsx_path), + node_binary, + }); + } + } + } + + if let Some(entry) = resolve_dist_entry(app) { + return Ok(Self { + entry, + runner: Runner::Node, + runner_path: None, + node_binary, + }); + } + + Err(anyhow::anyhow!( + "Unable to locate CodeNomad CLI build (dist/bin.js). Please build @neuralnomads/codenomad." + )) + } + + fn build_args(&self, dev: bool, host: &str) -> Vec { + let mut args = vec![ + "serve".to_string(), + "--host".to_string(), + host.to_string(), + "--port".to_string(), + "0".to_string(), + ]; + if dev { + args.push("--ui-dev-server".to_string()); + args.push("http://localhost:3000".to_string()); + args.push("--log-level".to_string()); + args.push("debug".to_string()); + } + args + } + + fn runner_args(&self, cli_args: &[String]) -> Vec { + let mut args = VecDeque::new(); + if self.runner == Runner::Tsx { + if let Some(path) = &self.runner_path { + args.push_back(path.clone()); + } + } + args.push_back(self.entry.clone()); + for arg in cli_args { + args.push_back(arg.clone()); + } + args.into_iter().collect() + } +} + +fn resolve_tsx(_app: &AppHandle) -> Option { + let candidates = vec![ + std::env::current_dir() + .ok() + .map(|p| p.join("node_modules/tsx/dist/cli.js")), + std::env::current_exe() + .ok() + .and_then(|ex| ex.parent().map(|p| p.join("../node_modules/tsx/dist/cli.js"))), + ]; + + first_existing(candidates) +} + +fn resolve_dev_entry(_app: &AppHandle) -> Option { + let candidates = vec![ + std::env::current_dir() + .ok() + .map(|p| p.join("packages/server/src/index.ts")), + std::env::current_dir() + .ok() + .map(|p| p.join("../server/src/index.ts")), + ]; + + first_existing(candidates) +} + +fn resolve_dist_entry(_app: &AppHandle) -> Option { + let base = workspace_root(); + let mut candidates: Vec> = vec![ + base.as_ref().map(|p| p.join("packages/server/dist/bin.js")), + base.as_ref().map(|p| p.join("packages/server/dist/index.js")), + base.as_ref().map(|p| p.join("server/dist/bin.js")), + base.as_ref().map(|p| p.join("server/dist/index.js")), + ]; + + if let Ok(exe) = std::env::current_exe() { + if let Some(dir) = exe.parent() { + let resources = dir.join("../Resources"); + candidates.push(Some(resources.join("server/dist/bin.js"))); + candidates.push(Some(resources.join("server/dist/index.js"))); + candidates.push(Some(resources.join("server/dist/server/bin.js"))); + candidates.push(Some(resources.join("server/dist/server/index.js"))); + candidates.push(Some(resources.join("resources/server/dist/bin.js"))); + candidates.push(Some(resources.join("resources/server/dist/index.js"))); + candidates.push(Some(resources.join("resources/server/dist/server/bin.js"))); + candidates.push(Some(resources.join("resources/server/dist/server/index.js"))); + + let linux_resource_roots = [dir.join("../lib/CodeNomad"), dir.join("../lib/codenomad")]; + for root in linux_resource_roots { + candidates.push(Some(root.join("server/dist/bin.js"))); + candidates.push(Some(root.join("server/dist/index.js"))); + candidates.push(Some(root.join("server/dist/server/bin.js"))); + candidates.push(Some(root.join("server/dist/server/index.js"))); + candidates.push(Some(root.join("resources/server/dist/bin.js"))); + candidates.push(Some(root.join("resources/server/dist/index.js"))); + candidates.push(Some(root.join("resources/server/dist/server/bin.js"))); + candidates.push(Some(root.join("resources/server/dist/server/index.js"))); + } + } + } + + first_existing(candidates) +} + +fn build_shell_command_string(entry: &CliEntry, cli_args: &[String]) -> anyhow::Result { + + let shell = default_shell(); + let mut quoted: Vec = Vec::new(); + quoted.push(shell_escape(&entry.node_binary)); + for arg in entry.runner_args(cli_args) { + quoted.push(shell_escape(&arg)); + } + let command = format!("ELECTRON_RUN_AS_NODE=1 exec {}", quoted.join(" ")); + let args = build_shell_args(&shell, &command); + log_line(&format!("user shell command: {} {:?}", shell, args)); + Ok(ShellCommand { shell, args }) +} + +fn default_shell() -> String { + if let Ok(shell) = std::env::var("SHELL") { + if !shell.trim().is_empty() { + return shell; + } + } + if cfg!(target_os = "macos") { + "/bin/zsh".to_string() + } else { + "/bin/bash".to_string() + } +} + +fn shell_escape(input: &str) -> String { + if input.is_empty() { + "''".to_string() + } else if !input + .chars() + .any(|c| matches!(c, ' ' | '"' | '\'' | '$' | '`' | '!' )) + { + input.to_string() + } else { + let escaped = input.replace('\'', "'\\''"); + format!("'{}'", escaped) + } +} + +fn build_shell_args(shell: &str, command: &str) -> Vec { + let shell_name = std::path::Path::new(shell) + .file_name() + .and_then(OsStr::to_str) + .unwrap_or("") + .to_lowercase(); + + if shell_name.contains("zsh") { + vec!["-l".into(), "-i".into(), "-c".into(), command.into()] + } else { + vec!["-l".into(), "-c".into(), command.into()] + } +} + +fn first_existing(paths: Vec>) -> Option { + paths + .into_iter() + .flatten() + .find(|p| p.exists()) + .map(|p| normalize_path(p)) +} + +fn normalize_path(path: PathBuf) -> String { + if let Ok(clean) = path.canonicalize() { + clean.to_string_lossy().to_string() + } else { + path.to_string_lossy().to_string() + } +} diff --git a/packages/tauri-app/src-tauri/src/main.rs b/packages/tauri-app/src-tauri/src/main.rs new file mode 100644 index 0000000..e92cce9 --- /dev/null +++ b/packages/tauri-app/src-tauri/src/main.rs @@ -0,0 +1,267 @@ +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +mod cli_manager; + +use cli_manager::{CliProcessManager, CliStatus}; +use serde_json::json; +use tauri::menu::{MenuBuilder, MenuItem, SubmenuBuilder}; +use tauri::plugin::{Builder as PluginBuilder, TauriPlugin}; +use tauri::webview::Webview; +use tauri::{AppHandle, Emitter, Manager, Runtime, Wry}; +use tauri_plugin_opener::OpenerExt; +use url::Url; + +#[derive(Clone)] +pub struct AppState { + pub manager: CliProcessManager, +} + +#[tauri::command] +fn cli_get_status(state: tauri::State) -> CliStatus { + state.manager.status() +} + +#[tauri::command] +fn cli_restart(app: AppHandle, state: tauri::State) -> Result { + let dev_mode = is_dev_mode(); + state.manager.stop().map_err(|e| e.to_string())?; + state + .manager + .start(app, dev_mode) + .map_err(|e| e.to_string())?; + Ok(state.manager.status()) +} + +fn is_dev_mode() -> bool { + cfg!(debug_assertions) || std::env::var("TAURI_DEV").is_ok() +} + +fn should_allow_internal(url: &Url) -> bool { + match url.scheme() { + "tauri" | "asset" | "file" => true, + "http" | "https" => matches!(url.host_str(), Some("127.0.0.1" | "localhost")), + _ => false, + } +} + +fn intercept_navigation(webview: &Webview, url: &Url) -> bool { + if should_allow_internal(url) { + return true; + } + + if let Err(err) = webview + .app_handle() + .opener() + .open_url(url.as_str(), None::<&str>) + { + eprintln!("[tauri] failed to open external link {}: {}", url, err); + } + false +} + +fn main() { + let navigation_guard: TauriPlugin = PluginBuilder::new("external-link-guard") + .on_navigation(|webview, url| intercept_navigation(webview, url)) + .build(); + + tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) + .plugin(tauri_plugin_opener::init()) + .plugin(navigation_guard) + .manage(AppState { + manager: CliProcessManager::new(), + }) + .setup(|app| { + build_menu(&app.handle())?; + let dev_mode = is_dev_mode(); + let app_handle = app.handle().clone(); + let manager = app.state::().manager.clone(); + std::thread::spawn(move || { + if let Err(err) = manager.start(app_handle.clone(), dev_mode) { + let _ = app_handle.emit("cli:error", json!({"message": err.to_string()})); + } + }); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![cli_get_status, cli_restart]) + .on_menu_event(|app_handle, event| { + match event.id().0.as_str() { + // File menu + "new_instance" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.emit("menu:newInstance", ()); + } + } + "close" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.close(); + } + } + "quit" => { + app_handle.exit(0); + } + + // View menu + "reload" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.eval("window.location.reload()"); + } + } + "force_reload" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.eval("window.location.reload(true)"); + } + } + "toggle_devtools" => { + if let Some(window) = app_handle.get_webview_window("main") { + window.open_devtools(); + } + } + + "toggle_fullscreen" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.set_fullscreen(!window.is_fullscreen().unwrap_or(false)); + } + } + + // Window menu + "minimize" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.minimize(); + } + } + "zoom" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.maximize(); + } + } + + // App menu (macOS) + "about" => { + // TODO: Implement about dialog + println!("About menu item clicked"); + } + "hide" => { + if let Some(window) = app_handle.get_webview_window("main") { + let _ = window.hide(); + } + } + "hide_others" => { + // TODO: Hide other app windows + println!("Hide Others menu item clicked"); + } + "show_all" => { + // TODO: Show all app windows + println!("Show All menu item clicked"); + } + + _ => { + println!("Unhandled menu event: {}", event.id().0); + } + } + }) + .build(tauri::generate_context!()) + .expect("error while building tauri application") + .run(|app_handle, event| match event { + tauri::RunEvent::ExitRequested { .. } => { + let app = app_handle.clone(); + std::thread::spawn(move || { + if let Some(state) = app.try_state::() { + let _ = state.manager.stop(); + } + app.exit(0); + }); + } + tauri::RunEvent::WindowEvent { + event: tauri::WindowEvent::Destroyed, + .. + } => { + if app_handle.webview_windows().len() <= 1 { + let app = app_handle.clone(); + std::thread::spawn(move || { + if let Some(state) = app.try_state::() { + let _ = state.manager.stop(); + } + app.exit(0); + }); + } + } + _ => {} + }); +} + +fn build_menu(app: &AppHandle) -> tauri::Result<()> { + let is_mac = cfg!(target_os = "macos"); + + // Create submenus + let mut submenus = Vec::new(); + + // App menu (macOS only) + if is_mac { + let app_menu = SubmenuBuilder::new(app, "CodeNomad") + .text("about", "About CodeNomad") + .separator() + .text("hide", "Hide CodeNomad") + .text("hide_others", "Hide Others") + .text("show_all", "Show All") + .separator() + .text("quit", "Quit CodeNomad") + .build()?; + submenus.push(app_menu); + } + + // File menu - create New Instance with accelerator + let new_instance_item = MenuItem::with_id( + app, + "new_instance", + "New Instance", + true, + Some("CmdOrCtrl+N") + )?; + + let file_menu = SubmenuBuilder::new(app, "File") + .item(&new_instance_item) + .separator() + .text(if is_mac { "close" } else { "quit" }, if is_mac { "Close" } else { "Quit" }) + .build()?; + submenus.push(file_menu); + + // Edit menu with predefined items for standard functionality + let edit_menu = SubmenuBuilder::new(app, "Edit") + .undo() + .redo() + .separator() + .cut() + .copy() + .paste() + .separator() + .select_all() + .build()?; + submenus.push(edit_menu); + + // View menu + let view_menu = SubmenuBuilder::new(app, "View") + .text("reload", "Reload") + .text("force_reload", "Force Reload") + .text("toggle_devtools", "Toggle Developer Tools") + .separator() + + .separator() + .text("toggle_fullscreen", "Toggle Full Screen") + .build()?; + submenus.push(view_menu); + + // Window menu + let window_menu = SubmenuBuilder::new(app, "Window") + .text("minimize", "Minimize") + .text("zoom", "Zoom") + .build()?; + submenus.push(window_menu); + + // Build the main menu with all submenus + let submenu_refs: Vec<&dyn tauri::menu::IsMenuItem<_>> = submenus.iter().map(|s| s as &dyn tauri::menu::IsMenuItem<_>).collect(); + let menu = MenuBuilder::new(app).items(&submenu_refs).build()?; + + app.set_menu(menu)?; + Ok(()) +} diff --git a/packages/tauri-app/src-tauri/tauri.conf.json b/packages/tauri-app/src-tauri/tauri.conf.json new file mode 100644 index 0000000..e847379 --- /dev/null +++ b/packages/tauri-app/src-tauri/tauri.conf.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "CodeNomad", + "version": "0.1.0", + "identifier": "ai.opencode.client", + "build": { + "beforeDevCommand": "npm run dev:bootstrap", + "beforeBuildCommand": "npm run bundle:server", + "frontendDist": "resources/ui-loading" + }, + + + + "app": { + "withGlobalTauri": true, + "windows": [ + { + "label": "main", + "title": "CodeNomad", + "url": "loading.html", + "width": 1400, + "height": 900, + "minWidth": 800, + "minHeight": 600, + "center": true, + "resizable": true, + "fullscreen": false, + "decorations": true, + "theme": "Dark", + "backgroundColor": "#1a1a1a", + "zoomHotkeysEnabled": true + } + ], + "security": { + "assetProtocol": { + "scope": ["**"] + }, + "capabilities": ["main-window-native-dialogs"] + } + }, + "bundle": { + "active": true, + "resources": [ + "resources/server", + "resources/ui-loading" + ], + "icon": ["icon.icns", "icon.ico", "icon.png"], + "targets": ["app", "appimage", "deb", "rpm", "nsis"] + } +} diff --git a/packages/ui/.gitignore b/packages/ui/.gitignore new file mode 100644 index 0000000..3ff38cc --- /dev/null +++ b/packages/ui/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.vite/ diff --git a/packages/ui/README.md b/packages/ui/README.md new file mode 100644 index 0000000..d59a42d --- /dev/null +++ b/packages/ui/README.md @@ -0,0 +1,54 @@ +# CodeNomad UI + +This package contains the frontend user interface for CodeNomad, built with [SolidJS](https://www.solidjs.com/) and [Tailwind CSS](https://tailwindcss.com/). + +## Overview + +The UI is designed to be a high-performance, low-latency cockpit for managing OpenCode sessions. It connects to the CodeNomad server (either running locally via CLI or embedded in the Electron app). + +## Features + +- **SolidJS**: Fine-grained reactivity for high performance. +- **Tailwind CSS**: Utility-first styling for rapid development. +- **Vite**: Fast build tool and dev server. + +## Development + +To run the UI in standalone mode (connected to a running server): + +```bash +npm run dev +``` + +This starts the Vite dev server at `http://localhost:3000`. + +## Building + +To build the production assets: + +``` +npm run build +``` + +The output will be generated in the `dist` directory, which is then consumed by the Server or Electron app. + +## Debug Logging + +The UI now routes all logging through a lightweight wrapper around [`debug`](https://github.com/debug-js/debug). The logger exposes four namespaces that can be toggled at runtime: + +- `sse` – Server-sent event transport and handlers +- `api` – HTTP/API calls and workspace lifecycle +- `session` – Session/model state, prompt handling, tool calls +- `actions` – User-driven interactions in UI components + +You can enable or disable namespaces from DevTools (in dev or production builds) via the global `window.codenomadLogger` helpers: + +```js +window.codenomadLogger?.listLoggerNamespaces() // => [{ name: "sse", enabled: false }, ...] +window.codenomadLogger?.enableLogger("sse") // turn on SSE logs +window.codenomadLogger?.disableLogger("sse") // turn them off again +window.codenomadLogger?.enableAllLoggers() // optional helper +``` + +Enabled namespaces are persisted in `localStorage` under `opencode:logger:namespaces`, so your preference survives reloads. + diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..000a515 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,40 @@ +{ + "name": "@codenomad/ui", + "version": "0.4.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "typecheck": "tsc --noEmit -p tsconfig.json", + "test": "node --test --experimental-strip-types src/lib/__tests__/*.test.ts src/stores/__tests__/*.test.ts" + }, + "dependencies": { + "@git-diff-view/solid": "^0.0.8", + "@kobalte/core": "0.13.11", + "@opencode-ai/sdk": "^1.0.138", + "@solidjs/router": "^0.13.0", + "@suid/icons-material": "^0.9.0", + "@suid/material": "^0.19.0", + "@suid/system": "^0.14.0", + "debug": "^4.4.3", + "github-markdown-css": "^5.8.1", + "lucide-solid": "^0.300.0", + "marked": "^12.0.0", + "qrcode": "^1.5.3", + "shiki": "^3.13.0", + "solid-js": "^1.8.0", + "solid-toast": "^0.5.0" + }, + "devDependencies": { + "autoprefixer": "10.4.21", + "postcss": "8.5.6", + "tailwindcss": "3", + "tsx": "^4.21.0", + "typescript": "^5.3.0", + "vite": "^5.0.0", + "vite-plugin-solid": "^2.10.0", + "zod": "^3.25.76" + } +} diff --git a/packages/ui/postcss.config.js b/packages/ui/postcss.config.js new file mode 100644 index 0000000..d90d3d0 --- /dev/null +++ b/packages/ui/postcss.config.js @@ -0,0 +1,11 @@ +import { fileURLToPath } from "url" +import { dirname, resolve } from "path" + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +export default { + plugins: { + tailwindcss: { config: resolve(__dirname, "tailwind.config.js") }, + autoprefixer: {}, + }, +} diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx new file mode 100644 index 0000000..d16d90e --- /dev/null +++ b/packages/ui/src/App.tsx @@ -0,0 +1,480 @@ +import { Component, For, Show, createMemo, createEffect, createSignal, onMount, onCleanup } from "solid-js" +import { Dialog } from "@kobalte/core/dialog" +import { Toaster } from "solid-toast" +import AlertDialog from "./components/alert-dialog" +import FolderSelectionView from "./components/folder-selection-view" +import { showConfirmDialog } from "./stores/alerts" +import InstanceTabs from "./components/instance-tabs" +import InstanceDisconnectedModal from "./components/instance-disconnected-modal" +import InstanceShell from "./components/instance/instance-shell2" +import { RemoteAccessOverlay } from "./components/remote-access-overlay" +import { InstanceMetadataProvider } from "./lib/contexts/instance-metadata-context" +import { initMarkdown } from "./lib/markdown" +import QwenOAuthCallback from "./pages/QwenOAuthCallback" + +import { useTheme } from "./lib/theme" +import { useCommands } from "./lib/hooks/use-commands" +import { useAppLifecycle } from "./lib/hooks/use-app-lifecycle" +import { getLogger } from "./lib/logger" +import { initReleaseNotifications } from "./stores/releases" +import { runtimeEnv } from "./lib/runtime-env" +import { + hasInstances, + isSelectingFolder, + setIsSelectingFolder, + showFolderSelection, + setShowFolderSelection, + showFolderSelectionOnStart, + setShowFolderSelectionOnStart, +} from "./stores/ui" +import { useConfig } from "./stores/preferences" +import { + createInstance, + instances, + activeInstanceId, + setActiveInstanceId, + stopInstance, + getActiveInstance, + disconnectedInstance, + acknowledgeDisconnectedInstance, +} from "./stores/instances" +import { + getSessions, + activeSessionId, + setActiveParentSession, + clearActiveParentSession, + createSession, + fetchSessions, + updateSessionAgent, + updateSessionModel, +} from "./stores/sessions" + +const log = getLogger("actions") + +const App: Component = () => { + const { isDark } = useTheme() + const { + preferences, + recordWorkspaceLaunch, + toggleShowThinkingBlocks, + toggleShowTimelineTools, + toggleAutoCleanupBlankSessions, + toggleUsageMetrics, + setDiffViewMode, + setToolOutputExpansion, + setDiagnosticsExpansion, + setThinkingBlocksExpansion, + } = useConfig() + const [escapeInDebounce, setEscapeInDebounce] = createSignal(false) + interface LaunchErrorState { + message: string + binaryPath: string + missingBinary: boolean + } + const [launchError, setLaunchError] = createSignal(null) + const [isAdvancedSettingsOpen, setIsAdvancedSettingsOpen] = createSignal(false) + const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false) + const [instanceTabBarHeight, setInstanceTabBarHeight] = createSignal(0) + + const shouldShowFolderSelection = () => !hasInstances() || showFolderSelectionOnStart() + + const updateInstanceTabBarHeight = () => { + if (typeof document === "undefined") return + const element = document.querySelector(".tab-bar-instance") + setInstanceTabBarHeight(element?.offsetHeight ?? 0) + } + + createEffect(() => { + void initMarkdown(isDark()).catch((error) => log.error("Failed to initialize markdown", error)) + }) + + createEffect(() => { + initReleaseNotifications() + }) + + createEffect(() => { + instances() + hasInstances() + requestAnimationFrame(() => updateInstanceTabBarHeight()) + }) + + onMount(() => { + updateInstanceTabBarHeight() + const handleResize = () => updateInstanceTabBarHeight() + window.addEventListener("resize", handleResize) + onCleanup(() => window.removeEventListener("resize", handleResize)) + }) + + const activeInstance = createMemo(() => getActiveInstance()) + const activeSessionIdForInstance = createMemo(() => { + const instance = activeInstance() + if (!instance) return null + return activeSessionId().get(instance.id) || null + }) + + const launchErrorPath = () => { + const value = launchError()?.binaryPath + if (!value) return "opencode" + return value.trim() || "opencode" + } + + const launchErrorMessage = () => launchError()?.message ?? "" + + const formatLaunchErrorMessage = (error: unknown): string => { + if (!error) { + return "Failed to launch workspace" + } + const raw = typeof error === "string" ? error : error instanceof Error ? error.message : String(error) + try { + const parsed = JSON.parse(raw) + if (parsed && typeof parsed.error === "string") { + return parsed.error + } + } catch { + // ignore JSON parse errors + } + return raw + } + + const isMissingBinaryMessage = (message: string): boolean => { + const normalized = message.toLowerCase() + return ( + normalized.includes("opencode binary not found") || + normalized.includes("binary not found") || + normalized.includes("no such file or directory") || + normalized.includes("binary is not executable") || + normalized.includes("enoent") + ) + } + + const clearLaunchError = () => setLaunchError(null) + + async function handleSelectFolder(folderPath: string, binaryPath?: string) { + if (!folderPath) { + return + } + setIsSelectingFolder(true) + const selectedBinary = binaryPath || preferences().lastUsedBinary || "opencode" + try { + recordWorkspaceLaunch(folderPath, selectedBinary) + clearLaunchError() + const instanceId = await createInstance(folderPath, selectedBinary) + setShowFolderSelection(false) + setShowFolderSelectionOnStart(false) + setIsAdvancedSettingsOpen(false) + + log.info("Created instance", { + instanceId, + port: instances().get(instanceId)?.port, + }) + } catch (error) { + const message = formatLaunchErrorMessage(error) + const missingBinary = isMissingBinaryMessage(message) + setLaunchError({ + message, + binaryPath: selectedBinary, + missingBinary, + }) + log.error("Failed to create instance", error) + } finally { + setIsSelectingFolder(false) + } + } + + function handleLaunchErrorClose() { + clearLaunchError() + } + + function handleLaunchErrorAdvanced() { + clearLaunchError() + setIsAdvancedSettingsOpen(true) + } + + function handleNewInstanceRequest() { + if (hasInstances()) { + setShowFolderSelection(true) + } + } + + async function handleDisconnectedInstanceClose() { + try { + await acknowledgeDisconnectedInstance() + } catch (error) { + log.error("Failed to finalize disconnected instance", error) + } + } + + async function handleCloseInstance(instanceId: string) { + const confirmed = await showConfirmDialog( + "Stop OpenCode instance? This will stop the server.", + { + title: "Stop instance", + variant: "warning", + confirmLabel: "Stop", + cancelLabel: "Keep running", + }, + ) + + if (!confirmed) return + + await stopInstance(instanceId) + } + + async function handleNewSession(instanceId: string) { + try { + const session = await createSession(instanceId) + setActiveParentSession(instanceId, session.id) + } catch (error) { + log.error("Failed to create session", error) + } + } + + async function handleCloseSession(instanceId: string, sessionId: string) { + const sessions = getSessions(instanceId) + const session = sessions.find((s) => s.id === sessionId) + + if (!session) { + return + } + + const parentSessionId = session.parentId ?? session.id + const parentSession = sessions.find((s) => s.id === parentSessionId) + + if (!parentSession || parentSession.parentId !== null) { + return + } + + clearActiveParentSession(instanceId) + + try { + await fetchSessions(instanceId) + } catch (error) { + log.error("Failed to refresh sessions after closing", error) + } + } + + const handleSidebarAgentChange = async (instanceId: string, sessionId: string, agent: string) => { + if (!instanceId || !sessionId || sessionId === "info") return + await updateSessionAgent(instanceId, sessionId, agent) + } + + const handleSidebarModelChange = async ( + instanceId: string, + sessionId: string, + model: { providerId: string; modelId: string }, + ) => { + if (!instanceId || !sessionId || sessionId === "info") return + await updateSessionModel(instanceId, sessionId, model) + } + + const { commands: paletteCommands, executeCommand } = useCommands({ + preferences, + toggleAutoCleanupBlankSessions, + toggleShowThinkingBlocks, + toggleShowTimelineTools, + toggleUsageMetrics, + setDiffViewMode, + setToolOutputExpansion, + setDiagnosticsExpansion, + setThinkingBlocksExpansion, + handleNewInstanceRequest, + handleCloseInstance, + handleNewSession, + handleCloseSession, + getActiveInstance: activeInstance, + getActiveSessionIdForInstance: activeSessionIdForInstance, + }) + + useAppLifecycle({ + setEscapeInDebounce, + handleNewInstanceRequest, + handleCloseInstance, + handleNewSession, + handleCloseSession, + showFolderSelection, + setShowFolderSelection, + getActiveInstance: activeInstance, + getActiveSessionIdForInstance: activeSessionIdForInstance, + }) + + // Listen for Tauri menu events + onMount(() => { + if (runtimeEnv.host === "tauri") { + const tauriBridge = (window as { __TAURI__?: { event?: { listen: (event: string, handler: (event: { payload: unknown }) => void) => Promise<() => void> } } }).__TAURI__ + if (tauriBridge?.event) { + let unlistenMenu: (() => void) | null = null + + tauriBridge.event.listen("menu:newInstance", () => { + handleNewInstanceRequest() + }).then((unlisten) => { + unlistenMenu = unlisten + }).catch((error) => { + log.error("Failed to listen for menu:newInstance event", error) + }) + + onCleanup(() => { + unlistenMenu?.() + }) + } + } + }) + + // Check if this is OAuth callback + const isOAuthCallback = window.location.pathname === '/auth/qwen/callback' + + if (isOAuthCallback) { + return + } + + return ( + <> + + + + + +
+ +
+ Unable to launch OpenCode + + We couldn't start the selected OpenCode binary. Review the error output below or choose a different + binary from Advanced Settings. + +
+ +
+

Binary path

+

{launchErrorPath()}

+
+ + +
+

Error output

+
{launchErrorMessage()}
+
+
+ +
+ + + + +
+
+
+
+
+
+ + setRemoteAccessOpen(true)} + /> + + + {(instance) => { + const isActiveInstance = () => activeInstanceId() === instance.id + const isVisible = () => isActiveInstance() && !showFolderSelection() + return ( +
+ + handleCloseSession(instance.id, sessionId)} + onNewSession={() => handleNewSession(instance.id)} + handleSidebarAgentChange={(sessionId, agent) => handleSidebarAgentChange(instance.id, sessionId, agent)} + handleSidebarModelChange={(sessionId, model) => handleSidebarModelChange(instance.id, sessionId, model)} + onExecuteCommand={executeCommand} + tabBarOffset={instanceTabBarHeight()} + /> + + +
+ ) + + }} +
+ + + } + > + setIsAdvancedSettingsOpen(true)} + onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)} + onOpenRemoteAccess={() => setRemoteAccessOpen(true)} + /> +
+ + +
+
+ + setIsAdvancedSettingsOpen(true)} + onAdvancedSettingsClose={() => setIsAdvancedSettingsOpen(false)} + /> +
+
+
+ + setRemoteAccessOpen(false)} /> + + + + +
+ + ) +} + + +export default App diff --git a/packages/ui/src/components/advanced-settings-modal.tsx b/packages/ui/src/components/advanced-settings-modal.tsx new file mode 100644 index 0000000..ba8c3eb --- /dev/null +++ b/packages/ui/src/components/advanced-settings-modal.tsx @@ -0,0 +1,136 @@ +import { Component, createSignal, Show } from "solid-js" +import { Dialog } from "@kobalte/core/dialog" +import OpenCodeBinarySelector from "./opencode-binary-selector" +import EnvironmentVariablesEditor from "./environment-variables-editor" +import OllamaCloudSettings from "./settings/OllamaCloudSettings" +import QwenCodeSettings from "./settings/QwenCodeSettings" +import ZAISettings from "./settings/ZAISettings" +import OpenCodeZenSettings from "./settings/OpenCodeZenSettings" + +interface AdvancedSettingsModalProps { + open: boolean + onClose: () => void + selectedBinary: string + onBinaryChange: (binary: string) => void + isLoading?: boolean +} + +const AdvancedSettingsModal: Component = (props) => { + const [activeTab, setActiveTab] = createSignal("general") + + return ( + !open && props.onClose()}> + + +
+ +
+ Advanced Settings +
+ +
+
+ + + + + +
+
+ +
+ + + + + +
+ + +
+
+

Environment Variables

+

Applied whenever a new OpenCode instance starts

+
+
+ +
+
+
+
+ + + + + + + + + + + + +
+ +
+ +
+
+
+
+
+ ) +} + +export default AdvancedSettingsModal diff --git a/packages/ui/src/components/agent-creator-dialog.tsx b/packages/ui/src/components/agent-creator-dialog.tsx new file mode 100644 index 0000000..106c94e --- /dev/null +++ b/packages/ui/src/components/agent-creator-dialog.tsx @@ -0,0 +1,327 @@ +import { Dialog } from "@kobalte/core/dialog" +import { Bot, Loader2, Sparkles, X } from "lucide-solid" +import { Component, Show, createSignal } from "solid-js" +import { Portal } from "solid-js/web" +import { updateInstanceConfig } from "../stores/instance-config" +import { fetchAgents } from "../stores/sessions" +import { showToastNotification } from "../lib/notifications" +import { getLogger } from "../lib/logger" + +const log = getLogger("agent-creator") + +const MAX_PROMPT_LENGTH = 30000 + +interface AgentCreatorDialogProps { + instanceId: string + open: boolean + onClose: () => void +} + +const AgentCreatorDialog: Component = (props) => { + const [name, setName] = createSignal("") + const [description, setDescription] = createSignal("") + const [prompt, setPrompt] = createSignal("") + const [isGenerating, setIsGenerating] = createSignal(false) + const [isSaving, setIsSaving] = createSignal(false) + const [useAiGeneration, setUseAiGeneration] = createSignal(true) + + const resetForm = () => { + setName("") + setDescription("") + setPrompt("") + setIsGenerating(false) + setUseAiGeneration(true) + } + + const handleClose = () => { + resetForm() + props.onClose() + } + + const generatePromptWithAI = async () => { + if (!name().trim() || !description().trim()) { + showToastNotification({ + title: "Missing Information", + message: "Please provide both name and description to generate an agent prompt.", + variant: "warning", + duration: 5000, + }) + return + } + + setIsGenerating(true) + try { + // Use Z.AI or another endpoint to generate the prompt + const response = await fetch("/api/zai/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: "glm-4.5-flash", + messages: [ + { + role: "system", + content: `You are an expert AI agent prompt designer. Generate a comprehensive, detailed system prompt for an AI coding assistant agent based on the user's requirements. The prompt should: +1. Define the agent's role and expertise +2. Specify its capabilities and limitations +3. Include guidelines for code style and best practices +4. Define how it should interact with users +5. Include any domain-specific knowledge relevant to the description + +Output ONLY the agent system prompt, no explanations or markdown formatting.`, + }, + { + role: "user", + content: `Create a system prompt for an AI coding agent with the following details: + +Name: ${name()} +Purpose: ${description()} + +Generate a comprehensive system prompt that will make this agent effective at its purpose.`, + }, + ], + stream: false, + max_tokens: 4096, + }), + }) + + if (!response.ok) { + throw new Error(`Generation failed: ${response.status}`) + } + + const data = await response.json() + const generatedPrompt = data?.choices?.[0]?.message?.content || data?.message?.content || "" + + if (generatedPrompt) { + setPrompt(generatedPrompt) + showToastNotification({ + title: "Prompt Generated", + message: "AI has generated a system prompt for your agent. Review and edit as needed.", + variant: "success", + duration: 5000, + }) + } else { + throw new Error("No prompt content in response") + } + } catch (error) { + log.error("Failed to generate agent prompt", error) + showToastNotification({ + title: "Generation Failed", + message: "Could not generate prompt. Please write one manually or check your Z.AI configuration.", + variant: "error", + duration: 8000, + }) + } finally { + setIsGenerating(false) + } + } + + const handleSave = async () => { + if (!name().trim()) { + showToastNotification({ + title: "Name Required", + message: "Please provide a name for the agent.", + variant: "warning", + duration: 5000, + }) + return + } + + if (!prompt().trim()) { + showToastNotification({ + title: "Prompt Required", + message: "Please provide a system prompt for the agent.", + variant: "warning", + duration: 5000, + }) + return + } + + setIsSaving(true) + try { + await updateInstanceConfig(props.instanceId, (draft) => { + if (!draft.customAgents) { + draft.customAgents = [] + } + // Check for duplicate names + const existing = draft.customAgents.findIndex((a) => a.name.toLowerCase() === name().toLowerCase()) + if (existing >= 0) { + // Update existing + draft.customAgents[existing] = { + name: name().trim(), + description: description().trim() || undefined, + prompt: prompt().trim(), + } + } else { + // Add new + draft.customAgents.push({ + name: name().trim(), + description: description().trim() || undefined, + prompt: prompt().trim(), + }) + } + }) + + // Refresh agents list + await fetchAgents(props.instanceId) + + showToastNotification({ + title: "Agent Created", + message: `Custom agent "${name()}" has been saved and is ready to use.`, + variant: "success", + duration: 5000, + }) + + handleClose() + } catch (error) { + log.error("Failed to save custom agent", error) + showToastNotification({ + title: "Save Failed", + message: "Could not save the agent. Please try again.", + variant: "error", + duration: 8000, + }) + } finally { + setIsSaving(false) + } + } + + return ( + !open && handleClose()}> + + +
+ + {/* Header */} +
+
+
+ +
+
+ Create Custom Agent + + Define a new AI agent with custom behavior and expertise + +
+
+ +
+ + {/* Content */} +
+ {/* Name Input */} +
+ + setName(e.currentTarget.value)} + placeholder="e.g., React Specialist, Python Expert, Code Reviewer..." + class="w-full px-3 py-2 bg-zinc-800 border border-zinc-600 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500 transition-colors" + /> +
+ + {/* Description Input */} +
+ + setDescription(e.currentTarget.value)} + placeholder="A few words about what this agent specializes in..." + class="w-full px-3 py-2 bg-zinc-800 border border-zinc-600 rounded-lg text-white placeholder:text-zinc-500 focus:outline-none focus:border-indigo-500 transition-colors" + /> +
+ + {/* Generation Mode Toggle */} +
+ + +
+ + {/* AI Generation Button */} + + + + + {/* Prompt Textarea */} +
+
+ + + {prompt().length.toLocaleString()} / {MAX_PROMPT_LENGTH.toLocaleString()} + +
+