diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..140fab3b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +data +tmp +results + +.env \ No newline at end of file diff --git a/.env.example b/.env.example index e13240b9..28ed6080 100644 --- a/.env.example +++ b/.env.example @@ -2,20 +2,74 @@ OPENAI_ENDPOINT=https://api.openai.com/v1 OPENAI_API_KEY= ANTHROPIC_API_KEY= +ANTHROPIC_ENDPOINT=https://api.anthropic.com GOOGLE_API_KEY= AZURE_OPENAI_ENDPOINT= AZURE_OPENAI_API_KEY= +AZURE_OPENAI_API_VERSION=2025-01-01-preview DEEPSEEK_ENDPOINT=https://api.deepseek.com DEEPSEEK_API_KEY= +MISTRAL_API_KEY= +MISTRAL_ENDPOINT=https://api.mistral.ai/v1 + +OLLAMA_ENDPOINT=http://localhost:11434 + +ALIBABA_ENDPOINT=https://dashscope.aliyuncs.com/compatible-mode/v1 +ALIBABA_API_KEY= + +MODELSCOPE_ENDPOINT=https://api-inference.modelscope.cn/v1 +MODELSCOPE_API_KEY= + +MOONSHOT_ENDPOINT=https://api.moonshot.cn/v1 +MOONSHOT_API_KEY= + +UNBOUND_ENDPOINT=https://api.getunbound.ai +UNBOUND_API_KEY= + +SiliconFLOW_ENDPOINT=https://api.siliconflow.cn/v1/ +SiliconFLOW_API_KEY= + +IBM_ENDPOINT=https://us-south.ml.cloud.ibm.com +IBM_API_KEY= +IBM_PROJECT_ID= + +GROK_ENDPOINT="https://api.x.ai/v1" +GROK_API_KEY= + +#set default LLM +DEFAULT_LLM=openai + + # Set to false to disable anonymized telemetry -ANONYMIZED_TELEMETRY=true +ANONYMIZED_TELEMETRY=false # LogLevel: Set to debug to enable verbose logging, set to result to get results only. Available: result | debug | info BROWSER_USE_LOGGING_LEVEL=info -CHROME_PATH= -CHROME_USER_DATA= \ No newline at end of file +# Browser settings +BROWSER_PATH= +BROWSER_USER_DATA= +BROWSER_DEBUGGING_PORT=9222 +BROWSER_DEBUGGING_HOST=localhost +# Set to true to keep browser open between AI tasks +KEEP_BROWSER_OPEN=true +USE_OWN_BROWSER=false +BROWSER_CDP= +# Display settings +# Format: WIDTHxHEIGHTxDEPTH +RESOLUTION=1920x1080x24 +# Width in pixels +RESOLUTION_WIDTH=1920 +# Height in pixels +RESOLUTION_HEIGHT=1080 + +# VNC settings +VNC_PASSWORD=youvncpassword + +# MCP (Model Context Protocol) settings +# Path to MCP server configuration file (default: ./mcp.json) +MCP_CONFIG_PATH= diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..87b41736 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,124 @@ +name: Build Docker Image + +on: + release: + types: [published] + push: + branches: [main] + +env: + GITHUB_CR_REPO: ghcr.io/${{ github.repository }} + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + platform: + - linux/amd64 + - linux/arm64 + steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.GITHUB_CR_REPO }} + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + platforms: ${{ matrix.platform }} + labels: ${{ steps.meta.outputs.labels }} + tags: | + ${{ env.GITHUB_CR_REPO }} + build-args: | + TARGETPLATFORM=${{ matrix.platform }} + outputs: type=image,push-by-digest=true,name-canonical=true,push=true + + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + + merge: + runs-on: ubuntu-latest + needs: + - build + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: | + ${{ env.GITHUB_CR_REPO }} + tags: | + type=ref,event=branch + type=ref,event=pr + type=semver,pattern={{version}} + type=semver,pattern={{major}} + + - name: Docker tags + run: | + tags=$(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") + if [ -z "$tags" ]; then + echo "DOCKER_METADATA_OUTPUT_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV + tags="-t ${{ env.GITHUB_CR_REPO }}:${{ github.ref_name }}" + fi + echo "DOCKER_METADATA_TAGS=$tags" >> $GITHUB_ENV + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create ${{ env.DOCKER_METADATA_TAGS }} \ + $(printf '${{ env.GITHUB_CR_REPO }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.GITHUB_CR_REPO }}:${{ env.DOCKER_METADATA_OUTPUT_VERSION }} diff --git a/.gitignore b/.gitignore index c45cf011..6c3e1062 100644 --- a/.gitignore +++ b/.gitignore @@ -123,6 +123,9 @@ celerybeat.pid # Environments .env +.env.local +.env.development +.env.production .venv env/ venv/ @@ -130,6 +133,7 @@ ENV/ env.bak/ venv.bak/ test_env/ +myenv # Spyder project settings @@ -176,4 +180,29 @@ cookies.json AgentHistory.json cv_04_24.pdf AgentHistoryList.json -*.gif \ No newline at end of file +*.gif + +# For Sharing (.pem files) +.gradio/ + +# For Docker +# data/ # Commented out - we now use data/ for settings + +# Settings data directory (keep structure, ignore content) +data/* +!data/.gitkeep +!data/README.md + +# For Config Files (Current Settings) +.config.pkl +*.pdf + +# MCP Configuration (User-specific) +mcp.json +data/mcp.json + +workflow + +# IDE and Editor Config Directories +.claude/ +.cursor/ \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..35e8e373 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,11 @@ +{ + "recommendations": [ + "charliermarsh.ruff", + "astral-sh.ty", + "ms-python.python", + "ms-python.debugpy", + "tamasfe.even-better-toml", + "EditorConfig.EditorConfig", + "ms-azuretools.vscode-docker" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..919dbf0f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,73 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "WebUI: Run (Debug)", + "type": "debugpy", + "request": "launch", + "module": "webui", + "console": "integratedTerminal", + "justMyCode": true, + "env": { + "PYTHONPATH": "${workspaceFolder}" + }, + "serverReadyAction": { + "pattern": "Running on.*localhost:([0-9]+)", + "uriFormat": "http://localhost:%s", + "action": "openExternally" + } + }, + { + "name": "WebUI: Run on Port 8080 (Debug)", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/webui.py", + "args": ["--ip", "0.0.0.0", "--port", "8080"], + "console": "integratedTerminal", + "justMyCode": true, + "env": { + "PYTHONPATH": "${workspaceFolder}" + }, + "serverReadyAction": { + "pattern": "Running on.*localhost:([0-9]+)", + "uriFormat": "http://localhost:%s", + "action": "openExternally" + } + }, + { + "name": "WebUI: Custom Theme (Debug)", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/webui.py", + "args": ["--theme", "Ocean", "--ip", "127.0.0.1", "--port", "7788"], + "console": "integratedTerminal", + "justMyCode": true, + "env": { + "PYTHONPATH": "${workspaceFolder}" + }, + "serverReadyAction": { + "pattern": "Running on.*localhost:([0-9]+)", + "uriFormat": "http://localhost:%s", + "action": "openExternally" + } + }, + { + "name": "Pytest: Debug Current Test File", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": ["${file}", "-v", "-s"], + "console": "integratedTerminal", + "justMyCode": false + }, + { + "name": "Pytest: Debug All Tests", + "type": "debugpy", + "request": "launch", + "module": "pytest", + "args": ["tests/", "-v"], + "console": "integratedTerminal", + "justMyCode": false + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..a02bdfc6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,48 @@ +{ + // Python configuration + "python.analysis.typeCheckingMode": "basic", + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe", + + // Ruff formatter and linter + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" + } + }, + + // Ruff configuration + "ruff.lineLength": 100, + "ruff.lint.enable": true, + "ruff.format.enable": true, + + // ty type checker configuration + "ty.enable": true, + "ty.path": "${workspaceFolder}/.venv/Scripts/ty.exe", + + // UV package manager + "python.terminal.activateEnvironment": true, + + // File associations + "files.associations": { + "*.toml": "toml", + ".env*": "properties" + }, + + // Exclude from search/watch + "files.exclude": { + "**/__pycache__": true, + "**/*.pyc": true, + "**/*.pyo": true, + "**/.pytest_cache": true, + "**/.ruff_cache": true, + "**/uv.lock": false + }, + + // Terminal settings for UV + "terminal.integrated.env.windows": { + "UV_SYSTEM_PYTHON": "0" + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..1eb41d16 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,288 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "UV: Sync Dependencies", + "type": "shell", + "command": "uv", + "args": ["sync", "--all-groups"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "new", + "echo": true + }, + "problemMatcher": [] + }, + { + "label": "UV: Install Playwright Browsers", + "type": "shell", + "command": "playwright", + "args": ["install", "--with-deps"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "problemMatcher": [] + }, + { + "label": "WebUI: Run (Default)", + "type": "shell", + "command": "uv", + "args": ["run", "python", "webui.py"], + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "dedicated", + "focus": true, + "echo": true + }, + "problemMatcher": [], + "dependsOn": [] + }, + { + "label": "WebUI: Run (Custom Port 8080)", + "type": "shell", + "command": "uv", + "args": [ + "run", + "python", + "webui.py", + "--ip", + "0.0.0.0", + "--port", + "8080" + ], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated", + "focus": true + }, + "problemMatcher": [] + }, + { + "label": "WebUI: Run (Theme: Soft)", + "type": "shell", + "command": "uv", + "args": ["run", "python", "webui.py", "--theme", "Soft"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated", + "focus": true + }, + "problemMatcher": [] + }, + { + "label": "Ruff: Format Code", + "type": "shell", + "command": "uv", + "args": ["run", "ruff", "format", "."], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Ruff: Check & Fix", + "type": "shell", + "command": "uv", + "args": ["run", "ruff", "check", ".", "--fix"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": [] + }, + { + "label": "Ty: Type Check (Full)", + "type": "shell", + "command": "uv", + "args": ["run", "ty", "check", "."], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "shared" + }, + "problemMatcher": { + "owner": "ty", + "fileLocation": ["relative", "${workspaceFolder}"], + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(error|warning):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + }, + { + "label": "Ty: Type Check (Watch Mode)", + "type": "shell", + "command": "uv", + "args": ["run", "ty", "check", ".", "--watch"], + "group": "build", + "isBackground": true, + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": { + "owner": "ty", + "fileLocation": ["relative", "${workspaceFolder}"], + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(error|warning):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + }, + "background": { + "activeOnStart": true, + "beginsPattern": "^Checking", + "endsPattern": "^(Found|No errors)" + } + } + }, + { + "label": "Ty: Type Check (Current File)", + "type": "shell", + "command": "uv", + "args": ["run", "ty", "check", "${file}"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": false + }, + "problemMatcher": { + "owner": "ty", + "fileLocation": ["relative", "${workspaceFolder}"], + "pattern": { + "regexp": "^(.*):(\\d+):(\\d+):\\s+(error|warning):\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + } + } + }, + { + "label": "Ty: Type Check (Verbose)", + "type": "shell", + "command": "uv", + "args": ["run", "ty", "check", ".", "--verbose"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + }, + { + "label": "Pytest: Run All Tests", + "type": "shell", + "command": "uv", + "args": ["run", "pytest", "-v"], + "group": "test", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + }, + { + "label": "Pytest: Run with Coverage", + "type": "shell", + "command": "uv", + "args": ["run", "pytest", "--cov=src", "--cov-report=html", "-v"], + "group": "test", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + }, + { + "label": "Docker: Build Image", + "type": "shell", + "command": "docker", + "args": ["compose", "up", "--build"], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "dedicated" + }, + "problemMatcher": [] + }, + { + "label": "Code Quality: Full Check", + "dependsOn": [ + "Ruff: Check & Fix", + "Ty: Type Check (Full)", + "Pytest: Run All Tests" + ], + "dependsOrder": "sequence", + "group": "build", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "problemMatcher": [] + }, + { + "label": "Setup: Complete Environment", + "dependsOn": ["UV: Sync Dependencies", "UV: Install Playwright Browsers"], + "dependsOrder": "sequence", + "group": "build", + "presentation": { + "reveal": "always", + "panel": "new" + }, + "problemMatcher": [] + }, + { + "label": "Kill Port 7788", + "type": "shell", + "command": "powershell", + "args": [ + "-Command", + "$line = netstat -ano | findstr :7788 | Select-Object -First 1; if ($line) { $tokens = -split $line.Trim(); $processId = $tokens[-1]; Write-Host \"Killing process $processId on port 7788...\"; taskkill /PID $processId /F 2>&1 | Out-Null; Write-Host \"Process killed successfully.\" } else { Write-Host \"No process found on port 7788.\" }" + ], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": false + }, + "problemMatcher": [] + }, + { + "label": "Kill Port 8080", + "type": "shell", + "command": "powershell", + "args": [ + "-Command", + "$line = netstat -ano | findstr :8080 | Select-Object -First 1; if ($line) { $tokens = -split $line.Trim(); $processId = $tokens[-1]; Write-Host \"Killing process $processId on port 8080...\"; taskkill /PID $processId /F 2>&1 | Out-Null; Write-Host \"Process killed successfully.\" } else { Write-Host \"No process found on port 8080.\" }" + ], + "group": "build", + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": false + }, + "problemMatcher": [] + } + ] +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..73661aba --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,387 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is **Browser Use Web UI** - a fork of [browser-use/web-ui](https://github.com/browser-use/web-ui) enhanced with UV backend, Python 3.14t support, and modern dependency management. It provides a Gradio-based web interface for AI agents that can control web browsers using the [browser-use](https://github.com/browser-use/browser-use) library. + +**Key Features:** + +- Multi-LLM support (OpenAI, Anthropic, Google, DeepSeek, Ollama, Azure, IBM Watson, etc.) +- Custom browser integration (use your own Chrome/browser profile) +- Persistent browser sessions between AI tasks +- High-definition screen recording +- MCP (Model Context Protocol) client integration + +## Development Commands + +This project uses **UV** for Python package management and supports Python 3.11-3.14t (free-threaded variant). + +### Environment Setup + +```bash +# Install Python 3.14t (recommended) or 3.11+ +uv python install 3.14t + +# Create virtual environment +uv venv --python 3.14t + +# Activate environment +# Windows CMD: +.venv\Scripts\activate +# Windows PowerShell: +.\.venv\Scripts\Activate.ps1 +# macOS/Linux: +source .venv/bin/activate + +# Install dependencies +uv sync + +# Install Playwright browsers +playwright install --with-deps +# Or specific browser: +playwright install chromium --with-deps +``` + +### Running the Application + +```bash +# Basic run (default: 127.0.0.1:7788) +python webui.py + +# With custom IP/port +python webui.py --ip 0.0.0.0 --port 8080 + +# With different theme +python webui.py --theme Ocean +# Available themes: Default, Soft, Monochrome, Glass, Origin, Citrus, Ocean, Base +``` + +### Testing + +```bash +# Run all tests +pytest + +# Run specific test file +pytest tests/test_agents.py + +# Run with verbose output +pytest -v + +# Run with async mode +pytest --asyncio-mode=auto +``` + +### Code Quality + +```bash +# Format code +ruff format . + +# Lint code +ruff check . + +# Fix linting issues automatically +ruff check . --fix + +# Type checking (using ty - Astral's Rust-based type checker) +# Note: ty is in alpha (0.0.1a23), expect potential bugs +ty check . +``` + +### Docker Development + +```bash +# Build and run with Docker Compose +docker compose up --build + +# For ARM64 systems (Apple Silicon) +TARGETPLATFORM=linux/arm64 docker compose up --build + +# Access web UI: http://localhost:7788 +# Access VNC viewer: http://localhost:6080/vnc.html +``` + +## Architecture + +The project follows a modular architecture under `src/web_ui/`: + +### Core Modules + +**`agent/`** - AI Agent implementations + +- `browser_use/browser_use_agent.py` - Main browser agent with enhanced signal handling, Ctrl+C support, and tool calling method auto-detection +- `deep_research/deep_research_agent.py` - Specialized research agent from Agent Marketplace + +**`browser/`** - Browser management + +- `custom_browser.py` - Custom browser initialization with support for user's own Chrome/browser +- `custom_context.py` - Browser context management for persistent sessions + +**`controller/`** - Action controllers + +- `custom_controller.py` - Extended controller with custom actions and MCP integration +- Registers actions like clipboard operations, content extraction, and assistant callbacks + +**`utils/`** - Shared utilities + +- `llm_provider.py` - LLM provider factory supporting 15+ LLM providers (OpenAI, Anthropic, Google, Azure, DeepSeek, Ollama, Mistral, IBM Watson, AWS Bedrock, etc.) +- `mcp_client.py` - Model Context Protocol client setup and tool registration +- `mcp_config.py` - MCP configuration file loading, validation, and management +- `config.py` - Configuration management +- `utils.py` - Common utilities + +**`webui/`** - Gradio UI components + +- `interface.py` - Main UI creation and theming +- `webui_manager.py` - State management for UI +- `components/` - Individual tab components: + - `agent_settings_tab.py` - LLM configuration UI + - `browser_settings_tab.py` - Browser configuration UI + - `mcp_settings_tab.py` - MCP server configuration UI + - `browser_use_agent_tab.py` - Agent execution UI + - `deep_research_agent_tab.py` - Research agent UI + - `load_save_config_tab.py` - Config persistence UI + +### Key Architectural Patterns + +1. **Custom Agent Extension**: The project extends `browser_use.agent.service.Agent` with `BrowserUseAgent` to add: + - Auto-detection of tool calling methods based on LLM provider + - Signal handling for Ctrl+C pause/resume + - Playwright script generation and GIF creation + +2. **Controller Pattern**: Extends `browser_use.controller.service.Controller` with custom actions like clipboard operations and content extraction + +3. **LLM Provider Abstraction**: Single factory function (`get_llm_model()`) returns LangChain chat model instances for any supported provider based on configuration + +4. **MCP Integration**: Dynamic tool registration from MCP servers, converting MCP tools to LangChain-compatible tools + +5. **Gradio Component Structure**: Each UI tab is a separate component function that accepts `WebuiManager` for state coordination + +## Environment Configuration + +Create `.env` from `.env.example`: + +```bash +cp .env.example .env +``` + +**Critical Environment Variables:** + +- `DEFAULT_LLM` - Default LLM provider (e.g., `openai`, `anthropic`, `google`) +- `{PROVIDER}_API_KEY` - API keys for each LLM provider +- `BROWSER_PATH` - Path to Chrome/browser executable (for custom browser mode) +- `BROWSER_USER_DATA` - Browser profile directory (for custom browser mode) +- `KEEP_BROWSER_OPEN` - Keep browser open between tasks (default: `true`) +- `BROWSER_USE_LOGGING_LEVEL` - Log level: `result`, `info`, or `debug` + +## Important Notes + +### LLM Provider Integration + +- Tool calling method is auto-detected per provider in `BrowserUseAgent._set_tool_calling_method()` +- Some models don't support tool calling and fall back to `raw` mode +- Google Gemini uses native tool calling (returns `None` for auto-detection) +- OpenAI/Azure use `function_calling` mode + +### Browser Management + +- When `USE_OWN_BROWSER=true`, the app connects to your Chrome profile via debugging port +- Close all Chrome windows before enabling "Use Own Browser" mode +- Open the WebUI in a non-Chrome browser (Firefox/Edge) when using your Chrome profile + +### MCP (Model Context Protocol) + +**Model Context Protocol (MCP)** allows AI agents to use tools and capabilities from external servers. This project supports persistent MCP configuration via `data/mcp.json`. + +#### Quick Start + +1. **Create MCP Configuration:** + + ```bash + # Option 1: Use the Web UI + # Go to the "MCP Settings" tab and click "Load Example Config" + + # Option 2: Copy the example file + cp mcp.example.json data/mcp.json + ``` + +2. **Edit Configuration:** + Edit `data/mcp.json` to enable the MCP servers you need: + + ```json + { + "mcpServers": { + "filesystem": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/directory"], + "transport": "stdio" + }, + "brave-search": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-brave-search"], + "env": { + "BRAVE_API_KEY": "your_api_key_here" + }, + "transport": "stdio" + } + } + } + ``` + + **Note:** As of `langchain-mcp-adapters 0.1.0+`, each server configuration **must include** a `"transport": "stdio"` key. + +3. **Use the MCP Settings Tab:** + - Navigate to the **🔌 MCP Settings** tab in the Web UI + - Use the built-in editor to view, validate, and save your configuration + - Click "Load Example Config" to see all available MCP servers + - The configuration is automatically loaded when you start an agent + +#### Configuration File Locations + +- **Default:** `./data/mcp.json` (data directory) +- **Custom:** Set `MCP_CONFIG_PATH` environment variable +- **Example:** `./mcp.example.json` (reference, not loaded) + +The `data/mcp.json` file is gitignored by default (user-specific configuration). + +#### Popular MCP Servers + +| Server | Description | Configuration | +|--------|-------------|---------------| +| **filesystem** | Access local files and directories | Requires path argument | +| **fetch** | Make HTTP requests to external APIs | No configuration needed | +| **brave-search** | Web search via Brave Search API | Requires `BRAVE_API_KEY` | +| **github** | GitHub repository operations | Requires `GITHUB_PERSONAL_ACCESS_TOKEN` | +| **postgres** | PostgreSQL database operations | Requires database URL | +| **sqlite** | SQLite database operations | Requires database path | +| **memory** | Persistent memory for agents | No configuration needed | +| **puppeteer** | Browser automation capabilities | No configuration needed | + +See `mcp.example.json` for complete configuration examples. + +#### How It Works + +1. **Auto-Loading:** When an agent starts, it automatically loads `data/mcp.json` if it exists +2. **Tool Registration:** Tools from MCP servers are registered as `mcp.{server_name}.{tool_name}` +3. **Dynamic Usage:** Agents can discover and use MCP tools alongside built-in browser actions +4. **Hot Reload:** Use the "Clear" button in the Run Agent tab to reload agents with new MCP configuration + +#### MCP Configuration Structure + +```json +{ + "mcpServers": { + "server-name": { + "command": "npx", // Command to run (e.g., "npx", "python", "node") + "args": [ // Command arguments + "-y", + "@org/package-name" + ], + "env": { // Optional environment variables + "API_KEY": "value" + }, + "transport": "stdio" // Required: transport type (stdio, sse, websocket, streamable_http) + } + } +} +``` + +**⚠️ Breaking Change (langchain-mcp-adapters 0.1.0+):** +All MCP server configurations **must** include `"transport": "stdio"`. Most MCP servers use stdio transport for process-based communication. + +#### Web UI Features + +The **MCP Settings** tab provides: + +- **Live Editor:** Edit `data/mcp.json` with syntax highlighting +- **Validation:** Real-time validation of configuration structure +- **Server Summary:** View configured servers and their details +- **Example Loading:** One-click loading of example configurations +- **Save/Load:** Persistent configuration management + +#### Configuration Management + +**Via Web UI:** + +1. Go to the **MCP Settings** tab +2. Edit the JSON configuration +3. Click "Save Configuration" +4. Restart agents (use "Clear" button) to apply changes + +**Via File System:** + +1. Edit `data/mcp.json` directly in your editor +2. Restart the Web UI or use "Clear" + new agent task + +**Via Environment:** + +```bash +# Use custom config location +export MCP_CONFIG_PATH=/path/to/custom/data/mcp.json +python webui.py +``` + +#### Agent Settings Tab Integration + +The **Agent Settings** tab shows: + +- ✅ **Active Configuration:** Displays current `data/mcp.json` status +- 📊 **Server Summary:** Lists configured MCP servers +- 📁 **File Upload:** Temporary override via JSON file upload (if no `data/mcp.json` exists) + +#### Implementation Files + +- `src/web_ui/utils/mcp_config.py` - Configuration loading, validation, and management +- `src/web_ui/utils/mcp_client.py` - MCP client setup and tool registration +- `src/web_ui/controller/custom_controller.py` - Auto-loading and tool registration +- `src/web_ui/webui/components/mcp_settings_tab.py` - Web UI for editing configuration + +#### Troubleshooting + +**MCP tools not appearing:** + +1. Verify `data/mcp.json` exists and is valid (use MCP Settings tab validator) +2. Check browser console/terminal for MCP client errors +3. Ensure required environment variables (API keys) are set +4. Use "Clear" button to restart the agent with new configuration + +**Configuration not loading:** + +1. Check file path: `./data/mcp.json` or `$MCP_CONFIG_PATH` +2. Validate JSON syntax (no trailing commas, proper quotes) +3. Review logs for "Loaded MCP configuration from..." message + +**Server-specific issues:** + +- **Filesystem:** Ensure the specified path exists and is accessible +- **API-based servers:** Verify API keys are correct and have proper permissions +- **npm packages:** Run `npx -y @package/name` manually to test installation + +### Signal Handling + +- Agents support Ctrl+C to pause execution +- Press Ctrl+C once to pause, type 'r' to resume, 'q' to quit +- Second Ctrl+C forces exit +- Implemented via `browser_use.utils.SignalHandler` + +### Testing + +- Test structure: `tests/` with test files prefixed `test_*` +- Uses `pytest` with `pytest-asyncio` for async tests +- Test coverage includes agents, controllers, LLM API, and Playwright integration + +### Code Style + +- Uses Ruff for formatting and linting (100 char line length) +- Target: Python 3.14 +- Import sorting via isort (integrated in Ruff) +- Type checking via `ty` (alpha - handle with care) + +### Docker Notes + +- Includes VNC server for watching browser interactions +- Default VNC password: `youvncpassword` (change via `VNC_PASSWORD`) +- Uses `supervisord.conf` for process management diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..1546d84c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,102 @@ +FROM python:3.14-slim-bookworm + +# Set platform for multi-arch builds (Docker Buildx will set this) +ARG TARGETPLATFORM +ARG NODE_MAJOR=20 + +# Install system dependencies (removed libgconf-2-4) +RUN apt-get update && apt-get install -y \ + wget \ + netcat-traditional \ + gnupg \ + curl \ + unzip \ + xvfb \ + libxss1 \ + libnss3 \ + libnspr4 \ + libasound2 \ + libatk1.0-0 \ + libatk-bridge2.0-0 \ + libcups2 \ + libdbus-1-3 \ + libdrm2 \ + libgbm1 \ + libgtk-3-0 \ + libxcomposite1 \ + libxdamage1 \ + libxfixes3 \ + libxrandr2 \ + xdg-utils \ + fonts-liberation \ + fonts-noto-color-emoji \ + fonts-unifont \ + dbus \ + xauth \ + x11vnc \ + tigervnc-tools \ + supervisor \ + net-tools \ + procps \ + git \ + python3-numpy \ + fontconfig \ + fonts-dejavu \ + fonts-dejavu-core \ + fonts-dejavu-extra \ + vim \ + && rm -rf /var/lib/apt/lists/* + +# Install UV - fast Python package manager +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +# Install noVNC +RUN git clone https://github.com/novnc/noVNC.git /opt/novnc \ + && git clone https://github.com/novnc/websockify /opt/novnc/utils/websockify \ + && ln -s /opt/novnc/vnc.html /opt/novnc/index.html + +# Install Node.js using NodeSource PPA +RUN mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ + && apt-get install -y nodejs \ + && rm -rf /var/lib/apt/lists/* + +# Verify Node.js and npm installation +RUN node -v && npm -v && npx -v + +# Set up working directory +WORKDIR /app + +# Copy dependency files +COPY pyproject.toml requirements.txt ./ + +# Set UV environment variables for better Docker performance +ENV UV_SYSTEM_PYTHON=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy + +# Install Python dependencies using UV +RUN uv pip install --system -r requirements.txt + +# Playwright setup +ENV PLAYWRIGHT_BROWSERS_PATH=/ms-browsers +RUN mkdir -p $PLAYWRIGHT_BROWSERS_PATH + +# Install Chromium via Playwright without --with-deps +RUN PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=0 playwright install chromium + +# Copy application code +COPY . . + +# Install project in editable mode if using pyproject.toml directly +RUN uv pip install --system -e . + +# Set up supervisor configuration +RUN mkdir -p /var/log/supervisor +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +EXPOSE 7788 6080 5901 9222 + +CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d77a86ed --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Browser Use Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 5d6363e0..cc6628c7 100644 --- a/README.md +++ b/README.md @@ -1,52 +1,196 @@ -# Browser-Use WebUI +Browser Use Web UI -## Background +
-This project builds upon the foundation of the [browser-use](https://github.com/browser-use/browser-use), which is designed to make websites accessible for AI agents. We have enhanced the original capabilities by providing: +[![GitHub stars](https://img.shields.io/github/stars/browser-use/web-ui?style=social)](https://github.com/browser-use/web-ui/stargazers) +[![Discord](https://img.shields.io/discord/1303749220842340412?color=7289DA&label=Discord&logo=discord&logoColor=white)](https://link.browser-use.com/discord) +[![Documentation](https://img.shields.io/badge/Documentation-📕-blue)](https://docs.browser-use.com) +[![WarmShao](https://img.shields.io/twitter/follow/warmshao?style=social)](https://x.com/warmshao) -1. **A Brand New WebUI:** We offer a comprehensive web interface that supports a wide range of `browser-use` functionalities. This UI is designed to be user-friendly and enables easy interaction with the browser agent. +This project builds upon the foundation of the [browser-use](https://github.com/browser-use/browser-use), which is designed to make websites accessible for AI agents. -2. **Expanded LLM Support:** We've integrated support for various Large Language Models (LLMs), including: Gemini, OpenAI, Azure OpenAI, Anthropic, DeepSeek, Ollama etc. And we plan to add support for even more models in the future. +We would like to officially thank [WarmShao](https://github.com/warmshao) for his contribution to this project. -3. **Custom Browser Support:** You can use your own browser with our tool, eliminating the need to re-login to sites or deal with other authentication challenges. This feature also supports high-definition screen recording. +**WebUI:** is built on Gradio and supports most of `browser-use` functionalities. This UI is designed to be user-friendly and enables easy interaction with the browser agent. -4. **Customized Agent:** We've implemented a custom agent that enhances `browser-use` with Optimized prompts. +**Expanded LLM Support:** We've integrated support for various Large Language Models (LLMs), including: Google, OpenAI, Azure OpenAI, Anthropic, DeepSeek, Ollama etc. And we plan to add support for even more models in the future. - +**Custom Browser Support:** You can use your own browser with our tool, eliminating the need to re-login to sites or deal with other authentication challenges. This feature also supports high-definition screen recording. -**Changelog** -- [x] **2025/01/06:** Thanks to @richard-devbot, a New and Well-Designed WebUI is released. [Video tutorial demo](https://github.com/warmshao/browser-use-webui/issues/1#issuecomment-2573393113). +**Persistent Browser Sessions:** You can choose to keep the browser window open between AI tasks, allowing you to see the complete history and state of AI interactions. + -## Environment Installation +## Installation Guide -1. **Python Version:** Ensure you have Python 3.11 or higher installed. -2. **Install `browser-use`:** - ```bash - pip install browser-use - ``` -3. **Install Playwright:** - ```bash - playwright install - ``` -4. **Install Dependencies:** - ```bash - pip install -r requirements.txt - ``` -5. **Configure Environment Variables:** - - Copy `.env.example` to `.env` and set your environment variables, including API keys for the LLM. - - **If using your own browser:** - - Set `CHROME_PATH` to the executable path of your browser (e.g., `C:\Program Files\Google\Chrome\Application\chrome.exe` on Windows). - - Set `CHROME_USER_DATA` to the user data directory of your browser (e.g.,`C:\Users\\AppData\Local\Google\Chrome\User Data`). +### Option 1: Windows UV Installation (Recommended) + +This guide focuses on Windows installation using UV package manager for optimal performance and modern Python development. + +#### Prerequisites + +- **Windows 10/11** (64-bit) +- **PowerShell 5.1+** or **PowerShell Core 7+** +- **Git** for Windows + +#### Step 1: Install UV Package Manager + +```powershell +# Install UV using winget (Windows Package Manager) +winget install astral-sh.uv + +# Or download from: https://github.com/astral-sh/uv/releases +``` + +#### Step 2: Clone the Repository + +```powershell +git clone https://github.com/browser-use/web-ui.git +cd web-ui +``` + +#### Step 3: Set Up Python Environment + +```powershell +# Install Python 3.14t (free-threaded variant) for best performance +uv python install 3.14t + +# Create virtual environment with Python 3.14t +uv venv --python 3.14t + +# Activate the virtual environment +.\.venv\Scripts\Activate.ps1 +``` + +> **Note:** Python 3.14t is the free-threaded variant that removes the Global Interpreter Lock (GIL) for better parallel performance. You can also use Python 3.11+ if preferred: `uv venv --python 3.11` + +#### Step 4: Install Dependencies + +```powershell +# Install all dependencies using UV (faster than pip) +uv sync + +# Install Playwright browsers +playwright install --with-deps + +# Or install specific browser +playwright install chromium --with-deps +``` + +#### Step 5: Configure Environment + +```powershell +# Copy environment template +Copy-Item .env.example .env + +# Edit .env with your API keys and settings +notepad .env +``` + +#### Step 6: Run the Application + +```powershell +# Start the WebUI +python webui.py + +# Or with custom settings +python webui.py --ip 0.0.0.0 --port 8080 --theme Ocean +``` + +### Option 2: Traditional pip Installation -## Usage +If you prefer using pip instead of UV: + +```powershell +# Create virtual environment +python -m venv .venv +.\.venv\Scripts\Activate.ps1 + +# Install dependencies +pip install -r requirements.txt +playwright install --with-deps +``` + +#### Step 4: Configure Environment + +1. Create a copy of the example environment file: + +- Windows (Command Prompt): + +```bash +copy .env.example .env +``` + +- macOS/Linux/Windows (PowerShell): + +```bash +cp .env.example .env +``` + +2. Open `.env` in your preferred text editor and add your API keys and other settings + +#### Step 5: Enjoy the web-ui + +1. **Run the WebUI:** -1. **Run the WebUI:** ```bash python webui.py --ip 127.0.0.1 --port 7788 ``` -2. **Access the WebUI:** Open your web browser and navigate to `http://127.0.0.1:7788`. -3. **Using Your Own Browser:** - - Close all chrome windows + +2. **Access the WebUI:** Open your web browser and navigate to `http://127.0.0.1:7788`. +3. **Using Your Own Browser(Optional):** + - Set `BROWSER_PATH` to the executable path of your browser and `BROWSER_USER_DATA` to the user data directory of your browser. Leave `BROWSER_USER_DATA` empty if you want to use local user data. + - Windows + + ```env + BROWSER_PATH="C:\Program Files\Google\Chrome\Application\chrome.exe" + BROWSER_USER_DATA="C:\Users\YourUsername\AppData\Local\Google\Chrome\User Data" + ``` + + > Note: Replace `YourUsername` with your actual Windows username for Windows systems. + - Mac + + ```env + BROWSER_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" + BROWSER_USER_DATA="/Users/YourUsername/Library/Application Support/Google/Chrome" + ``` + + - Close all Chrome windows - Open the WebUI in a non-Chrome browser, such as Firefox or Edge. This is important because the persistent browser context will use the Chrome data when running the agent. - Check the "Use Own Browser" option within the Browser Settings. + +### Option 3: Docker Installation (Alternative) + +> **Note:** Docker installation is available but not recommended for Windows users. The UV installation above provides better performance and easier debugging on Windows. + +#### Prerequisites + +- Docker Desktop for Windows +- WSL2 enabled (recommended) + +#### Quick Docker Setup + +```powershell +# Clone repository +git clone https://github.com/browser-use/web-ui.git +cd web-ui + +# Copy environment file +Copy-Item .env.example .env + +# Build and run with Docker Compose +docker compose up --build +``` + +#### Access Points + +- **Web-UI**: `http://localhost:7788` +- **VNC Viewer**: `http://localhost:6080/vnc.html` (password: "youvncpassword") + +> **Windows Users**: For better performance and easier debugging, we recommend using the UV installation method above instead of Docker. + +## Changelog + +- [x] **2025/01/26:** Thanks to @vvincent1234. Now browser-use-webui can combine with DeepSeek-r1 to engage in deep thinking! +- [x] **2025/01/10:** Thanks to @casistack. Now we have Docker Setup option and also Support keep browser open between tasks.[Video tutorial demo](https://github.com/browser-use/web-ui/issues/1#issuecomment-2582511750). +- [x] **2025/01/06:** Thanks to @richard-devbot. A New and Well-Designed WebUI is released. [Video tutorial demo](https://github.com/warmshao/browser-use-webui/issues/1#issuecomment-2573393113). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..f6c3df8a --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,19 @@ +## Reporting Security Issues + +If you believe you have found a security vulnerability in browser-use, please report it through coordinated disclosure. + +**Please do not report security vulnerabilities through the repository issues, discussions, or pull requests.** + +Instead, please open a new [Github security advisory](https://github.com/browser-use/web-ui/security/advisories/new). + +Please include as much of the information listed below as you can to help me better understand and resolve the issue: + +* The type of issue (e.g., buffer overflow, SQL injection, or cross-site scripting) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help me triage your report more quickly. diff --git a/assets/web-ui.png b/assets/web-ui.png new file mode 100644 index 00000000..383fffc3 Binary files /dev/null and b/assets/web-ui.png differ diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 00000000..c0b6ab9e --- /dev/null +++ b/data/.gitkeep @@ -0,0 +1,2 @@ +# This file ensures the data directory is tracked by git + diff --git a/data/README.md b/data/README.md new file mode 100644 index 00000000..3ad8aff9 --- /dev/null +++ b/data/README.md @@ -0,0 +1,90 @@ +# Settings Data Directory + +This directory contains persistent settings and configuration files for the Browser Use Web UI. + +## Directory Structure + +```plaintext +data/ +├── mcp.json # MCP (Model Context Protocol) server configuration +├── default_settings.json # Default settings loaded on startup +├── saved_configs/ # Archived timestamped configurations +│ └── YYYYMMDD-HHMMSS.json # Timestamped config snapshots +└── README.md # This file +``` + +## Files + +### `mcp.json` + +Model Context Protocol (MCP) server configuration. This file defines which MCP servers are available to agents and their configuration (API keys, paths, etc.). + +**Editing:** Use the MCP Settings tab in the Web UI or edit directly with a text editor. + +**Git:** This file is gitignored by default (user-specific configuration). + +### `default_settings.json` + +Automatically loaded when the application starts. Contains your preferred settings for: + +- LLM provider and model configuration +- Browser settings (headless, keep open, window size, etc.) +- MCP server configuration status +- Advanced agent parameters (max steps, temperature, etc.) + +Save current settings as default using the "💾 Save as Default" button in the Settings panel. + +### `saved_configs/` + +Directory containing timestamped copies of settings saved via the "💾 Save" button. Each file is named with the format `YYYYMMDD-HHMMSS.json`. + +These can be loaded later via the "📂 Load" button to restore previous configurations. + +## What Gets Saved + +**Persisted Settings:** + +- MCP server configuration (`mcp.json`) +- LLM provider and model name +- LLM parameters (temperature, vision enable, etc.) +- Browser configuration (headless, keep open, window size, paths) +- Agent parameters (max steps, max actions, tool calling method) +- System prompts (override/extend) + +**NOT Saved (Runtime Only):** + +- Chat history +- Agent task state +- Browser context/instances +- Controller instances +- File uploads +- Button states +- Current task execution state + +## Configuration Management + +### Loading Default Settings + +Default settings are automatically loaded on application startup. + +### Saving Default Settings + +1. Configure your settings in the Settings panel +2. Click "💾 Save as Default" button +3. Settings are saved to `default_settings.json` + +### Loading Previous Configuration + +1. Click "📂 Load" button +2. Select a `.json` file from `saved_configs/` directory (or upload your own) +3. Settings are restored and components update automatically + +### Creating Timestamped Backup + +1. Configure your settings +2. Click "💾 Save" button +3. A timestamped copy is saved to `saved_configs/YYYYMMDD-HHMMSS.json` + +## Migration + +Settings previously stored in `./tmp/webui_settings/` are automatically migrated to `./data/saved_configs/` on first startup after upgrading. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..97fdd2c4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,80 @@ +services: + # debug: docker compose run --rm -it browser-use-webui bash + browser-use-webui: + # image: ghcr.io/browser-use/web-ui # Using precompiled image + build: + context: . + dockerfile: Dockerfile + args: + TARGETPLATFORM: ${TARGETPLATFORM:-linux/amd64} + ports: + - "7788:7788" + - "6080:6080" + - "5901:5901" + - "9222:9222" + environment: + # LLM API Keys & Endpoints + - OPENAI_ENDPOINT=${OPENAI_ENDPOINT:-https://api.openai.com/v1} + - OPENAI_API_KEY=${OPENAI_API_KEY:-} + - ANTHROPIC_ENDPOINT=${ANTHROPIC_ENDPOINT:-https://api.anthropic.com} + - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - GOOGLE_API_KEY=${GOOGLE_API_KEY:-} + - AZURE_OPENAI_ENDPOINT=${AZURE_OPENAI_ENDPOINT:-} + - AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY:-} + - AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION:-2025-01-01-preview} + - DEEPSEEK_ENDPOINT=${DEEPSEEK_ENDPOINT:-https://api.deepseek.com} + - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY:-} + - OLLAMA_ENDPOINT=${OLLAMA_ENDPOINT:-http://localhost:11434} + - MISTRAL_ENDPOINT=${MISTRAL_ENDPOINT:-https://api.mistral.ai/v1} + - MISTRAL_API_KEY=${MISTRAL_API_KEY:-} + - ALIBABA_ENDPOINT=${ALIBABA_ENDPOINT:-https://dashscope.aliyuncs.com/compatible-mode/v1} + - ALIBABA_API_KEY=${ALIBABA_API_KEY:-} + - MOONSHOT_ENDPOINT=${MOONSHOT_ENDPOINT:-https://api.moonshot.cn/v1} + - MOONSHOT_API_KEY=${MOONSHOT_API_KEY:-} + - UNBOUND_ENDPOINT=${UNBOUND_ENDPOINT:-https://api.getunbound.ai} + - UNBOUND_API_KEY=${UNBOUND_API_KEY:-} + - SiliconFLOW_ENDPOINT=${SiliconFLOW_ENDPOINT:-https://api.siliconflow.cn/v1/} + - SiliconFLOW_API_KEY=${SiliconFLOW_API_KEY:-} + - IBM_ENDPOINT=${IBM_ENDPOINT:-https://us-south.ml.cloud.ibm.com} + - IBM_API_KEY=${IBM_API_KEY:-} + - IBM_PROJECT_ID=${IBM_PROJECT_ID:-} + + # Application Settings + - ANONYMIZED_TELEMETRY=${ANONYMIZED_TELEMETRY:-false} + - BROWSER_USE_LOGGING_LEVEL=${BROWSER_USE_LOGGING_LEVEL:-info} + + # Browser Settings + - BROWSER_PATH= + - BROWSER_USER_DATA= + - BROWSER_DEBUGGING_PORT=${BROWSER_DEBUGGING_PORT:-9222} + - BROWSER_DEBUGGING_HOST=localhost + - USE_OWN_BROWSER=false + - KEEP_BROWSER_OPEN=true + - BROWSER_CDP=${BROWSER_CDP:-} # e.g., http://localhost:9222 + + # Display Settings + - DISPLAY=:99 + # This ENV is used by the Dockerfile during build time if playwright respects it. + # It's not strictly needed at runtime by docker-compose unless your app or scripts also read it. + - PLAYWRIGHT_BROWSERS_PATH=/ms-browsers # Matches Dockerfile ENV + - RESOLUTION=${RESOLUTION:-1920x1080x24} + - RESOLUTION_WIDTH=${RESOLUTION_WIDTH:-1920} + - RESOLUTION_HEIGHT=${RESOLUTION_HEIGHT:-1080} + + # VNC Settings + - VNC_PASSWORD=${VNC_PASSWORD:-youvncpassword} + + volumes: + - /tmp/.X11-unix:/tmp/.X11-unix + # - ./my_chrome_data:/app/data/chrome_data # Optional: persist browser data + restart: unless-stopped + shm_size: "2gb" + cap_add: + - SYS_ADMIN + tmpfs: + - /tmp + healthcheck: + test: ["CMD", "nc", "-z", "localhost", "5901"] # VNC port + interval: 10s + timeout: 5s + retries: 3 diff --git a/docs/BACKEND_IMPROVEMENTS.md b/docs/BACKEND_IMPROVEMENTS.md new file mode 100644 index 00000000..334a7c6d --- /dev/null +++ b/docs/BACKEND_IMPROVEMENTS.md @@ -0,0 +1,1007 @@ +# Backend Improvements: LangGraph Memory & State Management + +## Current Architecture Analysis + +### Strengths + +- ✅ **DeepResearchAgent** uses LangGraph with `StateGraph` for workflow orchestration +- ✅ Existing infrastructure: `EventBus`, `AgentTracer`, `CostCalculator`, `WorkflowGraphBuilder` +- ✅ MCP integration for external tool access +- ✅ Observability framework with spans and traces + +### Gaps + +- ❌ **BrowserUseAgent** doesn't use LangGraph (uses browser-use's internal Agent class) +- ❌ No persistent checkpointing/state management for BrowserUseAgent +- ❌ No conversation memory or summarization +- ❌ Limited streaming support for workflows +- ❌ No retry logic or error recovery patterns + +--- + +## Recommended Improvements + +### 1. LangGraph-Based State Management for BrowserUseAgent + +**Current:** BrowserUseAgent uses a simple `for step in range(max_steps)` loop + +**Proposed:** Refactor to use LangGraph StateGraph with nodes: + +- `planning_node` - Analyze task and create plan +- `action_node` - Execute browser actions +- `observation_node` - Process results and extract information +- `decision_node` - Determine next action or completion +- `synthesis_node` - Aggregate results + +**Benefits:** + +- Better error recovery (can resume from any node) +- Checkpointing support (save/restore state) +- Parallel action execution +- Built-in observability + +### 2. Persistent Memory Implementation + +#### Short-Term Memory (Conversation Window Management) + +```python +from langchain_core.chat_history import InMemoryChatMessageHistory +from langgraph.checkpoint.memory import MemorySaver +from langgraph.prebuilt import ToolNode +from langchain_core.chat_history import BaseChatMessageHistory + +class ShortTermMemory: + """Manages conversation history within context window.""" + + def __init__(self, max_history_length: int = 50): + self.max_history_length = max_history_length + self.memory = InMemoryChatMessageHistory() + + def add_message(self, message: BaseMessage): + """Add message and trim if needed.""" + self.memory.add_message(message) + if len(self.memory.messages) > self.max_history_length: + self._trim_messages() + + def _trim_messages(self): + """Remove oldest messages or summarize.""" + # Keep system message + last N messages + # Or summarize older messages + pass +``` + +#### Long-Term Memory (Persistent Storage) + +```python +from langgraph.checkpoint.sqlite import SqliteSaver +from langchain_core.documents import Document +from langchain_community.vectorstores import Chroma + +class LongTermMemory: + """Persistent memory across sessions.""" + + def __init__(self, persist_directory: str = "./tmp/memory"): + self.checkpointer = SqliteSaver.from_conn_string("memory.db") + self.vectorstore = Chroma( + persist_directory=persist_directory, + embedding_function=self._get_embedding_fn() + ) + + async def save_episode(self, session_id: str, state: dict): + """Save agent execution episode.""" + await self.checkpointer.aput((session_id,), state) + + async def retrieve_similar(self, query: str, k: int = 5): + """Retrieve similar past experiences.""" + return self.vectorstore.similarity_search(query, k=k) +``` + +### 3. Enhanced Checkpointing + +**Current:** BrowserUseAgent saves final history as JSON/GIF + +**Proposed:** LangGraph checkpointing with SqliteSaver + +```python +from langgraph.checkpoint.sqlite import SqliteSaver + +def build_browser_agent_graph(): + workflow = StateGraph(BrowserAgentState) + + # Setup checkpointing + checkpointer = SqliteSaver.from_conn_string("checkpoints.db") + + workflow.add_node("plan", planning_node) + workflow.add_node("act", action_node) + workflow.add_node("observe", observation_node) + + # Compile with checkpointing + app = workflow.compile(checkpointer=checkpointer) + return app + +# Usage with checkpointing +thread_config = {"configurable": {"thread_id": task_id}} +final_state = await app.ainvoke(initial_state, config=thread_config) + +# Resume from checkpoint +current_state = await app.aget_state(thread_config) +``` + +### 4. Streaming Support + +**Proposed:** Add streaming for real-time updates + +```python +from langgraph.graph.message import add_messages + +async def stream_agent_execution(app, initial_state, thread_id): + """Stream agent execution updates.""" + config = {"configurable": {"thread_id": thread_id}} + + async for event in app.astream(initial_state, config=config): + # Yield events for UI updates + if event: + yield { + "node": list(event.keys())[0], + "state": event[list(event.keys())[0]], + "timestamp": time.time() + } +``` + +### 5. Error Recovery & Retry Logic + +```python +from langgraph.graph import StateGraph +from typing import Literal + +class BrowserAgentState(TypedDict): + messages: list[BaseMessage] + task: str + actions_taken: list[dict] + failures: int + max_retries: int + current_page: str + browser_state: dict + +def should_retry(state: BrowserAgentState) -> Literal["retry", "continue", "fail"]: + """Determine if we should retry failed action.""" + if state["failures"] < state["max_retries"]: + return "retry" + elif state["failures"] >= state["max_retries"]: + return "fail" + return "continue" + +# Add retry node +async def retry_node(state: BrowserAgentState) -> dict: + """Retry last failed action with different strategy.""" + last_action = state["actions_taken"][-1] + + # Adjust strategy (e.g., wait longer, try different selector) + return { + "failures": state["failures"] + 1, + "current_strategy": _get_next_strategy(state["failures"]) + } +``` + +### 6. Conversation Summarization + +```python +from langchain.chains.summarize import load_summarize_chain +from langchain_core.prompts import PromptTemplate + +class ConversationSummarizer: + """Summarize long conversations to save tokens.""" + + def __init__(self, llm): + self.llm = llm + self.summary_prompt = PromptTemplate( + input_variables=["text"], + template="Summarize the following conversation, focusing on: " + "1. Task objective 2. Key actions taken 3. Results found\n\n{text}" + ) + + async def summarize_history(self, messages: list[BaseMessage]) -> str: + """Condense conversation history.""" + # Convert messages to text + conversation_text = "\n".join([msg.content for msg in messages]) + + # Create chain + chain = load_summarize_chain(self.llm, chain_type="stuff") + + # Summarize + summary = await chain.ainvoke({"input_documents": [Document(page_content=conversation_text)]}) + return summary["output_text"] +``` + +### 7. Integration with Existing Observability + +**Proposed:** Enhance tracer to work with LangGraph + +```python +from src.web_ui.observability.tracer import AgentTracer + +class LangGraphTracer: + """Tracer for LangGraph workflows.""" + + def __init__(self, tracer: AgentTracer): + self.tracer = tracer + + async def trace_node(self, node_name: str, inputs: dict): + """Trace a LangGraph node execution.""" + async with self.tracer.span( + name=f"node:{node_name}", + span_type=SpanType.AGENT_NODE, + inputs=inputs + ) as span: + # Node execution + yield span +``` + +--- + +## Implementation Roadmap + +### Phase 1: Foundation (Week 1-2) + +1. ✅ Add LangGraph to BrowserUseAgent +2. ✅ Implement SqliteSaver checkpointing +3. ✅ Create BrowserAgentState TypedDict +4. ✅ Add basic workflow nodes (plan, act, observe) + +### Phase 2: Memory (Week 3-4) + +1. ✅ Implement ShortTermMemory for message trimming +2. ✅ Add LongTermMemory with vector store +3. ✅ Integrate conversation summarization +4. ✅ Add memory retrieval to planning node + +### Phase 3: Reliability (Week 5-6) + +1. ✅ Add retry logic and error recovery +2. ✅ Implement streaming support +3. ✅ Enhance observability integration +4. ✅ Add progress persistence + +### Phase 4: Optimization (Week 7-8) + +1. ✅ Optimize checkpoint frequency +2. ✅ Add parallel action execution +3. ✅ Implement result caching +4. ✅ Performance tuning + +--- + +## Code Structure + +``` +src/web_ui/agent/browser_use/ +├── browser_use_agent.py # Current (to be refactored) +├── langgraph_agent.py # NEW: LangGraph-based agent +├── state.py # NEW: State definitions +├── nodes.py # NEW: Workflow nodes +├── memory/ +│ ├── __init__.py +│ ├── short_term.py # NEW: Conversation memory +│ ├── long_term.py # NEW: Persistent memory +│ └── summarizer.py # NEW: Conversation summarization +└── checkpoints/ + ├── __init__.py + └── sqlite_checkpointer.py # NEW: Checkpoint management +``` + +--- + +## Example: New LangGraph-Based BrowserUseAgent + +```python +from langgraph.graph import StateGraph +from langgraph.checkpoint.sqlite import SqliteSaver +from typing import TypedDict + +class BrowserAgentState(TypedDict): + task: str + messages: list[BaseMessage] + browser_context: BrowserContext + actions_taken: list[dict] + current_url: str + page_html: str + failures: int + max_steps: int + current_step: int + +class LangGraphBrowserAgent: + def __init__(self, llm, browser, controller): + self.llm = llm + self.browser = browser + self.controller = controller + self.graph = self._build_graph() + + def _build_graph(self): + workflow = StateGraph(BrowserAgentState) + + # Add nodes + workflow.add_node("plan", self.planning_node) + workflow.add_node("act", self.action_node) + workflow.add_node("observe", self.observation_node) + workflow.add_node("decide", self.decision_node) + + # Setup edges + workflow.set_entry_point("plan") + workflow.add_edge("plan", "act") + workflow.add_edge("act", "observe") + workflow.add_edge("observe", "decide") + + # Conditional edge + workflow.add_conditional_edges( + "decide", + self.should_continue, + { + "act": "act", + "synthesize": "synthesize_node", + "end": "end_node" + } + ) + + # Compile with checkpointing + checkpointer = SqliteSaver.from_conn_string("browser_agent.db") + return workflow.compile(checkpointer=checkpointer) + + async def run(self, task: str, config: dict = None): + """Run agent with checkpointing support.""" + initial_state = { + "task": task, + "messages": [HumanMessage(content=task)], + "browser_context": self.browser, + "actions_taken": [], + "current_url": "", + "page_html": "", + "failures": 0, + "max_steps": 100, + "current_step": 0 + } + + # Use thread_id for checkpointing + if config is None: + config = {"configurable": {"thread_id": str(uuid.uuid4())}} + + # Stream execution for real-time updates + async for event in self.graph.astream(initial_state, config=config): + yield event + + # Get final state + final_state = await self.graph.aget_state(config) + return final_state +``` + +--- + +## Migration Strategy + +### Option 1: Gradual Migration (Recommended) + +1. Keep existing `BrowserUseAgent` as-is +2. Create new `LangGraphBrowserAgent` in parallel +3. Add feature flag to switch between implementations +4. Test thoroughly before full migration + +### Option 2: Full Refactor + +1. Refactor `BrowserUseAgent` to use LangGraph internally +2. Maintain same public API +3. Add checkpointing/memory as optional features + +--- + +## Dependencies to Add + +```toml +[dependencies] +langgraph = ">=0.3.34" # Already added +langchain-community = ">=0.3.0" # Already added +chromadb = ">=0.5.0" # NEW: Vector store +tiktoken = ">=0.7.0" # NEW: Token counting +sqlalchemy = ">=2.0.0" # NEW: For SqliteSaver +``` + +--- + +## Benefits Summary + +| Feature | Current | With Improvements | +|---------|---------|-------------------| +| State Persistence | ❌ None | ✅ Sqlite checkpointing | +| Resume Execution | ❌ Not possible | ✅ Resume from any checkpoint | +| Memory Management | ❌ None | ✅ Short + long-term memory | +| Error Recovery | ❌ Basic retry | ✅ Advanced retry logic | +| Streaming | ❌ Limited | ✅ Full streaming support | +| Observability | ⚠️ Partial | ✅ Full integration | +| Performance | ⚠️ Good | ✅ Optimized with caching | + +--- + +## Next Steps + +1. **Review** this document with the team +2. **Prioritize** features based on use cases +3. **Create** implementation tickets +4. **Start** with Phase 1 (foundation) +5. **Iterate** based on feedback + +--- + +## Questions? + +- Which features are highest priority? +- Should we implement gradual migration or full refactor? +- What's the target timeline? +- Any specific use cases we should prioritize? + +--- + +### Review (GPT-5) + +- Overall: Strong plan. Leverages existing `DeepResearchAgent` patterns and adds the missing foundations (checkpointing, memory, streaming) where `BrowserUseAgent` needs it most. +- Priority call: Start with a gradual migration via a new `LangGraphBrowserAgent` behind a feature flag, then cut over after parity tests pass. +- Short-term memory: Add trimming first; summarization can follow. Keep the heuristic simple initially (system + last N + rolling summary). +- Long-term memory: Defer vector DB until checkpoints and summaries are stable. Start with SQLite-only episode storage; add Chroma later if retrieval becomes a clear win. +- Checkpointing: Use `SqliteSaver` with per-thread `thread_id`. On Windows, set `PRAGMA journal_mode=WAL` and avoid sharing a single connection across threads to prevent locks. +- Streaming: Wire `app.astream()` into the existing `EventBus` so UI updates stay decoupled. Define a minimal event schema for node start/end and partial outputs. +- Reliability: Add explicit browser error classes (timeouts, stale element, navigation failures) and a bounded retry/backoff policy at the node level. +- Observability: Attach `AgentTracer` spans per LangGraph node and propagate token/cost from `llm_provider` so `CostCalculator` reflects real usage. +- Security/PII: Redact secrets in memory snapshots and streamed events. Reuse the `_sanitize_params()` approach from `workflow_graph`. +- Testing: Gate the migration with integration tests that assert deterministic node transitions, resumability from checkpoints, and Playwright/browser cleanup. + +Do first (minimal, high impact): + +1) Implement `LangGraphBrowserAgent` with `plan → act → observe → decide` and `SqliteSaver`. +2) Add feature flag and parity test path that runs both agents on the same seed task, comparing terminal outcomes. +3) Stream node lifecycle events through `EventBus` and render them in `workflow_visualizer`. +4) Add message trimming (no LLM summarization yet) and configurable history limits. +5) Introduce a small, typed retry policy with max attempts and exponential backoff. + +Risks and mitigations: + +- LangGraph refactor complexity → Mitigate with feature flag + parity tests + phased rollout. +- SQLite lock contention on Windows → Use WAL, one async connection per task/thread, and bounded checkpoint frequency. +- Playwright resource leaks → Add teardown guards, lint for awaited closures, and integration tests that assert clean shutdown. +- Token/cost drift → Centralize token accounting in one place (`llm_provider`), forward into spans, and surface totals in UI. + +Decisions needed: + +- Feature-flag name and default (recommend default ON for dev only). +- Summarization model/threshold (start off; enable after stability). +- Vector store choice and when to enable (defer until retrieval use-cases justify it). +- Event schema for streaming and retention policy for checkpoints. + +--- + +### Technical Review (Claude Sonnet 4.5) + +**Architecture Assessment:** + +✅ **Strong Points:** + +- LangGraph integration is the right choice - provides state machine formalism, checkpointing, and async-first design that aligns with browser automation's event-driven nature +- Gradual migration strategy minimizes risk while enabling feature-by-feature rollout - critical for production systems +- Leveraging existing `DeepResearchAgent` patterns ensures consistency and reduces learning curve +- EventBus + AgentTracer integration maintains separation of concerns and enables telemetry + +⚠️ **Architecture Concerns:** + +- BrowserUseAgent wraps browser-use's Agent base class - full LangGraph migration might break compatibility with upstream updates +- **Recommendation:** Use adapter pattern - keep browser-use Agent as-is, wrap it in LangGraph nodes rather than replacing internals +- State bloat risk: `BrowserAgentState` could grow large with full browser context + HTML + history - consider state partitioning +- Need clear boundaries between stateful (checkpointed) and ephemeral (runtime-only) data + +**Memory Management Deep Dive:** + +✅ **Short-Term Memory:** + +- Trimming strategy is correct first step - summarization adds complexity, latency, and LLM costs +- **Critical:** Use token-aware trimming (tiktoken) rather than message count for consistency across model context windows +- Consider sliding window with overlap (e.g., keep last 30 messages + summary of prior 100) to preserve context continuity +- **Pro tip:** Store trimmed messages separately for debugging/audit trails - invaluable for diagnosing agent failures + +⚠️ **Long-Term Memory Concerns:** + +- Vector store (ChromaDB) adds 200MB+ dependency weight plus embedding model overhead +- **Key question:** Do browser automation tasks truly benefit from semantic memory retrieval? Most tasks are linear, not associative +- **Alternative:** Structured episode storage (SQLite) with metadata indexing (task type, success/fail, duration, actions) may be sufficient +- If vector search is needed, consider lighter options: SQLite-VSS (5MB), DuckDB with vec extension (15MB) + +**Proposed Refinement - Multi-Tier Memory:** + +```python +class MemoryTier: + """Multi-tier memory strategy balancing performance and capability.""" + + # Tier 1: Hot memory (last N messages, in-memory) + hot_memory: list[BaseMessage] # Last 20-50 messages, ~1-2MB + hot_max_tokens: int = 8000 # Token limit, not message count + + # Tier 2: Warm memory (session summary, SQLite) + session_summary: str # Periodic condensation every 50 messages + key_learnings: list[str] # Extracted insights (optional) + + # Tier 3: Cold memory (historical episodes, indexed) + episode_index: dict[str, EpisodeMetadata] # Fast lookup by task_id, date, outcome + + # Tier 4: Vector search (only if retrieval use-case proven) + semantic_store: Optional[VectorStore] # Defer until Phase 3+ +``` + +**Checkpointing Implementation:** + +✅ **Good Approach:** + +- SqliteSaver is production-ready, battle-tested in LangGraph +- Thread-based isolation prevents state collision across concurrent tasks + +⚠️ **Windows-Specific Concerns (Critical for this project):** + +- SQLite on Windows has known issues with concurrent writes despite WAL mode +- **Recommendation:** Use `timeout` parameter (30s+) and implement exponential backoff on SQLITE_BUSY errors +- Consider file locking implications in WSL vs native Windows - test both environments +- **Performance:** Test checkpoint frequency under load - every node vs every N nodes vs time-based (every 30s) + +**Checkpoint Frequency Strategy:** + +```python +class CheckpointStrategy: + """Smart checkpointing to reduce I/O overhead.""" + + def should_checkpoint(self, state: dict, node_name: str, last_checkpoint: float) -> bool: + """Determine if we should checkpoint at this node.""" + # Checkpoint on: + # 1. Critical state transitions (planning → execution) + critical_nodes = ["planning_node", "synthesis_node"] + if node_name in critical_nodes: + return True + + # 2. Time threshold (every 30s to prevent data loss) + if time.time() - last_checkpoint > 30: + return True + + # 3. Action count (every 5 browser actions) + if state.get("actions_taken", 0) % 5 == 0: + return True + + # 4. Before expensive operations (page navigation, file download) + if node_name in ["navigate_node", "download_node"]: + return True + + return False +``` + +**Streaming Architecture:** + +✅ **Event-Driven Design:** + +- Integration with existing EventBus maintains architectural consistency +- Decoupled streaming enables multiple consumers (UI, logs, telemetry, debugging tools) + +🔧 **Implementation Recommendations:** + +**1. Event Schema Design:** + +```python +from dataclasses import dataclass +from typing import Literal + +@dataclass +class NodeEvent: + """Standard event for LangGraph node lifecycle.""" + event_type: Literal["node_start", "node_end", "node_error"] + node_name: str + timestamp: float + session_id: str + thread_id: str + + # Optional fields (populated based on event type) + inputs: Optional[dict] = None + outputs: Optional[dict] = None + duration_ms: Optional[float] = None + error: Optional[str] = None + metadata: Optional[dict] = None # Cost, tokens, action count, etc. +``` + +**2. Backpressure Handling (Critical for UI responsiveness):** + +```python +async def stream_with_backpressure(app, state, config, max_buffer=100): + """Stream with bounded buffer to prevent memory exhaustion.""" + buffer = asyncio.Queue(maxsize=max_buffer) + + async def producer(): + try: + async for event in app.astream(state, config): + await buffer.put(event) + except Exception as e: + await buffer.put(("error", e)) + finally: + await buffer.put(None) # Sentinel + + producer_task = asyncio.create_task(producer()) + + while True: + event = await buffer.get() + if event is None: + break + if isinstance(event, tuple) and event[0] == "error": + raise event[1] + yield event + + await producer_task +``` + +**Error Recovery & Retry:** + +✅ **Bounded Retry is Essential:** + +- Prevents infinite loops on persistent failures (e.g., element never appears) +- Exponential backoff reduces server load and respects rate limits + +🔧 **Enhanced Retry Strategy:** + +```python +from enum import Enum +from typing import List + +class BrowserErrorType(Enum): + NAVIGATION_TIMEOUT = "navigation_timeout" + ELEMENT_NOT_FOUND = "element_not_found" + STALE_ELEMENT = "stale_element" + NETWORK_ERROR = "network_error" + JAVASCRIPT_ERROR = "javascript_error" + PERMISSION_DENIED = "permission_denied" + +class RetryPolicy: + """Multi-level retry with context-aware strategies.""" + + def __init__(self): + self.strategies = { + BrowserErrorType.NAVIGATION_TIMEOUT: [ + "increase_timeout", # 30s → 60s → 90s + "refresh_page", # Hard refresh + "new_context" # New browser context + ], + BrowserErrorType.ELEMENT_NOT_FOUND: [ + "wait_longer", # 5s → 10s → 15s + "search_by_text", # Fall back to text search + "relaxed_selector" # Try parent or sibling elements + ], + BrowserErrorType.STALE_ELEMENT: [ + "refetch_element", # Query DOM again + "retry_action", # Retry with fresh element + "page_reload" # Last resort + ], + BrowserErrorType.NETWORK_ERROR: [ + "exponential_backoff", # 1s, 2s, 4s, 8s + "dns_refresh", # Clear DNS cache + "proxy_switch" # Try different network path + ] + } + + self.max_attempts = { + BrowserErrorType.NAVIGATION_TIMEOUT: 3, + BrowserErrorType.ELEMENT_NOT_FOUND: 5, + BrowserErrorType.STALE_ELEMENT: 3, + BrowserErrorType.NETWORK_ERROR: 4, + } + + def get_retry_strategy(self, error_type: BrowserErrorType, attempt: int) -> Optional[str]: + """Return strategy for given error and attempt number.""" + if attempt >= self.max_attempts.get(error_type, 3): + return None # Max retries exceeded + + strategies = self.strategies.get(error_type, ["exponential_backoff"]) + return strategies[min(attempt, len(strategies) - 1)] +``` + +**Code Organization Recommendations:** + +``` +src/web_ui/agent/browser_use/ +├── browser_use_agent.py # Legacy implementation (keep for now) +├── config.py # NEW: Feature flags and configuration +├── langgraph/ # NEW: LangGraph implementation +│ ├── __init__.py +│ ├── agent.py # Main LangGraphBrowserAgent +│ ├── state.py # TypedDict definitions +│ ├── nodes/ # Workflow nodes +│ │ ├── __init__.py +│ │ ├── planning.py # Task analysis and plan generation +│ │ ├── action.py # Browser action execution +│ │ ├── observation.py # Result extraction and processing +│ │ └── decision.py # Next action determination +│ ├── memory/ # Memory management +│ │ ├── __init__.py +│ │ ├── manager.py # Unified memory interface +│ │ ├── hot.py # In-memory cache (last N messages) +│ │ ├── warm.py # Session summaries (SQLite) +│ │ └── cold.py # Historical storage (SQLite + optional vector) +│ ├── checkpointing/ +│ │ ├── __init__.py +│ │ ├── sqlite_saver.py # Checkpoint management +│ │ └── strategy.py # Checkpoint policies (when to checkpoint) +│ ├── retry/ +│ │ ├── __init__.py +│ │ ├── policies.py # Retry strategies per error type +│ │ ├── backoff.py # Backoff algorithms (exponential, linear, etc.) +│ │ └── classifiers.py # Error classification logic +│ └── streaming/ +│ ├── __init__.py +│ ├── events.py # Event schemas +│ └── adapters.py # EventBus integration +└── utils/ + ├── __init__.py + ├── adapter.py # Legacy → LangGraph adapter + └── validators.py # State validation utilities +``` + +**Testing Strategy:** + +**Essential test coverage** (must have before GA): + +1. **State Persistence Tests:** + - Checkpoint at every node, kill process, resume from each checkpoint + - Verify state integrity after resume (no data loss, correct continuation) + - Test checkpoint corruption recovery + +2. **Memory Tests:** + - Validate trimming preserves system message and recent context + - Test token counting accuracy across different models + - Verify summarization doesn't lose critical task information + - Test retrieval relevance (if vector store implemented) + +3. **Retry Tests:** + - Assert exponential backoff timing (measure actual delays) + - Verify max attempts respected (doesn't retry forever) + - Test strategy switching (tries different approaches) + - Validate error classification accuracy + +4. **Streaming Tests:** + - Verify event ordering (start before end, no out-of-order) + - Test backpressure (doesn't crash on burst of 1000 events) + - Validate no data loss in events + - Test stream cancellation (clean shutdown) + +5. **Integration Tests (Most Important):** + - Run legacy vs LangGraph on 50+ real-world tasks + - Compare success rate, action count, execution time + - Validate output equivalence (same result, different path OK) + - Test edge cases: timeouts, errors, complex multi-step tasks + +6. **Performance Tests:** + - Measure checkpoint overhead per node (<100ms target) + - Track memory growth over 100-step task (<200MB target) + - Test streaming throughput (>100 events/sec) + - Profile end-to-end latency (baseline + 20% acceptable) + +7. **Failure Tests:** + - Browser crash mid-execution → should resume cleanly + - Network loss during action → should retry with backoff + - Checkpoint DB corruption → should detect and recover + - Out of memory → should trim aggressively and warn + +**Performance Benchmarks to Track:** + +| Metric | Baseline (Current) | Target (LangGraph) | Red Flag Threshold | +|--------|-------------------|--------------------|--------------------| +| Avg step latency | ~2-5s | ~2-6s | >7s | +| Memory per session | ~50-100MB | ~100-150MB | >200MB | +| Checkpoint write time | N/A | <100ms | >500ms | +| Resume time from checkpoint | N/A | <500ms | >2s | +| Event stream latency | ~100ms | ~50-100ms | >300ms | +| Token usage vs baseline | N/A | +0-10% | +30% | +| Success rate | ~85-90% | ~85-90% | <80% | + +**Migration Risk Assessment:** + +🔴 **High Risk:** + +- **Upstream breaking changes:** browser-use library could change APIs, invalidating wrapper approach + - *Mitigation:* Version pin browser-use, add integration tests for API surface, monitor upstream releases +- **SQLite corruption:** Crash during checkpoint write could corrupt database, lose session state + - *Mitigation:* Add checksums, implement corruption recovery, periodic validation, backup last-known-good checkpoint +- **Memory leaks:** Long-running sessions (100+ steps) could leak browser contexts, DOM references + - *Mitigation:* Add max session duration (30 min), periodic GC, browser context pooling, leak detection tests + +🟡 **Medium Risk:** + +- **Performance regression:** Checkpointing overhead could slow down fast tasks by 20-30% + - *Mitigation:* Profile early, add opt-out flag for performance mode, optimize checkpoint frequency +- **Increased complexity:** More moving parts make debugging harder, especially async issues + - *Mitigation:* Comprehensive docs, architecture diagrams, decision logs, enhanced logging +- **Dependency bloat:** ChromaDB (200MB), tiktoken (5MB), sqlalchemy (20MB) increase install size + - *Mitigation:* Make vector store optional, lazy-load dependencies, document minimal install + +🟢 **Low Risk:** + +- **Feature flag enables safe rollback:** Can switch back to legacy with single config change +- **Gradual migration limits blast radius:** Only affects opt-in users initially +- **Existing DeepResearchAgent proves viability:** LangGraph already proven in production workload + +**Mitigation Strategies:** + +1. **Upstream Compatibility:** + - Version pin: `browser-use==0.1.48` in `pyproject.toml` + - Add integration tests for browser-use API surface (Agent.**init**, .run(), .step()) + - Set up GitHub webhook to monitor browser-use releases, review breaking changes + +2. **Checkpoint Integrity:** + - Add CRC32 checksums to checkpoint metadata + - Implement auto-recovery: detect corruption, fall back to previous checkpoint + - Periodic validation: background task checks checkpoint integrity every 5 minutes + - Keep last 3 checkpoints (not just latest) for multi-level fallback + +3. **Memory Management:** + - Hard limit: max session duration 30 minutes, then force checkpoint and restart + - Periodic GC: every 50 actions, explicitly close old browser tabs, clear caches + - Browser context pooling: reuse contexts when possible, limit max open contexts to 3 + - Leak detection: track object counts (pages, contexts, DOM elements) in tests + +4. **Performance:** + - Profile early: add instrumentation from day 1, track all metrics in observability + - Add opt-out: `DISABLE_CHECKPOINTING=true` environment variable for performance mode + - Optimize checkpoint frequency: start conservative (every node), tune based on data + +5. **Complexity:** + - Architecture decision records (ADRs): document why each choice was made + - Sequence diagrams: visual flow of state transitions, checkpointing, streaming + - Enhanced logging: structured logs with correlation IDs, trace agent execution path + - Decision logs: capture key decisions as they're made during implementation + +**Immediate Action Items (Week-by-Week Breakdown):** + +**Week 1 - Foundation (Must Have):** + +- [ ] Create feature flag system: `config.py` with `USE_LANGGRAPH_AGENT=false` default +- [ ] Implement minimal `BrowserAgentState` TypedDict (task, messages, actions_taken, current_url) +- [ ] Build basic workflow: `START → planning_node → action_node → END` +- [ ] Add SqliteSaver with WAL mode, timeout handling (30s), and exponential backoff +- [ ] Create adapter that wraps browser-use Agent methods in LangGraph node functions +- [ ] Write unit tests for state transitions and adapter + +**Week 2 - Parity (Must Have):** + +- [ ] Implement all workflow nodes (planning, action, observation, decision) +- [ ] Add conditional edges and routing logic (`should_continue`, `should_retry`) +- [ ] Create integration test suite: run same 20 tasks on legacy vs LangGraph, compare outputs +- [ ] Implement checkpointing: checkpoint at every critical node (planning, synthesis) +- [ ] Add basic streaming via EventBus: node start/end events +- [ ] Performance baseline: measure latency, memory, success rate + +**Week 3 - Memory (Should Have):** + +- [ ] Implement hot memory: last N messages with token-aware trimming (tiktoken) +- [ ] Add configurable limits: max tokens (8000), max messages (50) +- [ ] Build session summary generation: template-based, no LLM (saves cost) +- [ ] Create memory manager interface: `MemoryManager.add_message()`, `.get_context()` +- [ ] Add memory tests: verify trimming, token counting, context preservation +- [ ] Integrate memory into planning node: prepend summary to planning prompt + +**Week 4 - Reliability (Should Have):** + +- [ ] Implement retry policies: exponential backoff, max attempts per error type +- [ ] Add error classification: navigation, element, network, JavaScript errors +- [ ] Build strategy-based retry: different approach per error type +- [ ] Add observability integration: AgentTracer spans per node, cost tracking +- [ ] Performance profiling: identify bottlenecks, optimize hot paths +- [ ] Load testing: 100 concurrent tasks, measure checkpoint contention + +**Week 5-8 - Nice to Have (Defer if needed):** + +- [ ] LLM-based summarization (optional, adds cost) +- [ ] Vector store integration (optional, evaluate need first) +- [ ] Parallel action execution (complex, high risk) +- [ ] Result caching (optimization, not critical) +- [ ] Advanced telemetry and dashboards + +**Architectural Decisions Log:** + +**Document these decisions with rationale:** + +1. **Wrapper vs Full Refactor:** + - **Decision:** Wrapper approach (keep browser-use Agent, wrap in LangGraph nodes) + - **Rationale:** Maintains compatibility with upstream, reduces risk, enables gradual migration + - **Trade-off:** Extra abstraction layer, but safer + +2. **Memory Strategy:** + - **Decision:** Defer vector store, focus on structured episode storage + hot memory + - **Rationale:** Browser tasks are mostly linear, not associative; vector search has marginal benefit + - **Trade-off:** Less sophisticated, but faster and cheaper + +3. **Checkpoint Frequency:** + - **Decision:** Start with checkpoint at every critical node, optimize based on profiling + - **Rationale:** Prioritize data safety over performance initially + - **Trade-off:** Higher I/O overhead, but prevents data loss + +4. **Event Schema:** + - **Decision:** Adopt lightweight schema (node_name, timestamp, inputs/outputs), add fields incrementally + - **Rationale:** Simpler to implement, easier to evolve + - **Trade-off:** May need to version schema later + +5. **Feature Flag Default:** + - **Decision:** `OFF` for production, `ON` for dev/staging + - **Rationale:** Minimize risk in production, enable testing in non-prod + - **Trade-off:** Requires manual enablement for testing + +6. **Summarization:** + - **Decision:** Defer LLM-based summarization until Phase 3+, use templates initially + - **Rationale:** Reduces complexity, cost, and latency; template-based is sufficient for v1 + - **Trade-off:** Less sophisticated summaries, but faster + +7. **Testing Threshold:** + - **Decision:** Require 95%+ parity with legacy agent before GA (same success rate) + - **Rationale:** High bar ensures quality, prevents regressions + - **Trade-off:** Takes longer to ship, but safer + +**Final Recommendation:** + +**GO with gradual migration.** The plan is technically sound and addresses real limitations, but: + +**✅ Do This:** + +- Start with MVP: checkpointing + basic workflow (4 nodes: plan → act → observe → decide) +- Add memory tier-by-tier based on proven need (hot first, warm later, cold/vector defer) +- Measure everything from day 1: latency, memory, checkpoint size, success rate +- Keep escape hatch: per-user or per-task ability to fall back to legacy agent +- Document as you go: capture architecture decisions, learnings, trade-offs + +**❌ Don't Do This:** + +- Don't build all features upfront - ship incrementally, validate each layer +- Don't add vector store until you have concrete use-case demonstrating need +- Don't optimize prematurely - profile first, optimize hot paths only +- Don't skip testing - integration tests are more important than unit tests here +- Don't forget Windows-specific issues - SQLite, file locking, path handling all differ + +**Timeline Assessment:** + +The 8-week timeline is **reasonable but aggressive**. To hit it: + +- Cut scope ruthlessly: focus on core features (checkpointing, workflow, streaming) +- Defer optimizations: memory tiers, retry strategies, vector store can wait +- Accept technical debt: ship MVP, refactor later with learnings +- Allocate 30% buffer for unexpected issues (SQLite quirks, browser-use API changes) + +**Suggested Prioritization (MoSCoW):** + +**Must Have (Week 1-2):** + +- Feature flag + basic LangGraph workflow +- Checkpointing with SqliteSaver (resume support) +- Streaming integration with EventBus +- Integration tests (legacy vs LangGraph parity) + +**Should Have (Week 3-4):** + +- Hot memory management (token-aware trimming) +- Retry policies (exponential backoff, max attempts) +- Observability integration (AgentTracer spans, cost tracking) +- Performance profiling and optimization + +**Could Have (Week 5-6):** + +- Session summaries (template-based first, LLM later) +- Advanced error recovery (strategy-based retry) +- Load testing and concurrency optimization + +**Won't Have (v1, revisit later):** + +- Vector store / semantic memory retrieval +- Parallel action execution +- LLM-based conversation summarization +- Result caching / memoization + +**Ship Fast, Iterate:** +Ship Phase 1 (checkpointing + workflow) as internal beta in Week 2, gather feedback, iterate. Don't wait for perfection - the infrastructure is solid, execution matters more than planning. + +**Success Criteria:** + +- ✅ Resume from checkpoint works 100% of the time (no data loss) +- ✅ Performance within 20% of baseline (acceptable overhead) +- ✅ Success rate matches or exceeds legacy (95%+ parity) +- ✅ No regressions in existing functionality +- ✅ Clear rollback path if issues arise diff --git a/docs/LANGGRAPH_MIGRATION_PLAN.md b/docs/LANGGRAPH_MIGRATION_PLAN.md new file mode 100644 index 00000000..3505703a --- /dev/null +++ b/docs/LANGGRAPH_MIGRATION_PLAN.md @@ -0,0 +1,1086 @@ +# LangGraph Migration Plan: BrowserUseAgent Enhancement + +**Status:** Planning Phase +**Target Timeline:** 8 weeks (with 30% buffer) +**Priority:** High - Foundation for improved reliability and state management +**Last Updated:** October 22, 2025 + +--- + +## Executive Summary + +This plan outlines the migration of `BrowserUseAgent` to a LangGraph-based architecture, enabling: + +- **State persistence** via checkpointing (resume interrupted sessions) +- **Memory management** (conversation trimming, session summaries) +- **Streaming updates** (real-time UI feedback) +- **Error recovery** (smart retry with context-aware strategies) +- **Observability** (detailed tracing and cost tracking) + +**Approach:** Gradual migration with feature flag, preserving legacy agent as fallback. + +**Key Success Metrics:** + +- 95%+ parity with legacy agent (success rate, output quality) +- <500ms checkpoint write time +- <2s resume time from checkpoint +- +20% performance overhead acceptable +- 100% resumability (no data loss on crash) + +--- + +## Current State Analysis + +### Architecture Overview + +``` +Current: BrowserUseAgent (browser-use lib) +├── Simple for loop: range(max_steps) +├── No state persistence +├── No memory management +└── Limited error recovery + +Existing Infrastructure: +├── DeepResearchAgent (already uses LangGraph) ✅ +├── EventBus (pub/sub pattern) ✅ +├── AgentTracer (observability) ✅ +├── CostCalculator (token/cost tracking) ✅ +└── WorkflowGraphBuilder (visualization) ✅ +``` + +### Gaps Identified + +| Gap | Impact | Priority | +|-----|--------|----------| +| No checkpointing | Can't resume after crash/interruption | 🔴 High | +| No memory management | Context window overflow on long tasks | 🔴 High | +| Limited streaming | Poor UI responsiveness | 🟡 Medium | +| Basic retry logic | Fails unnecessarily on transient errors | 🟡 Medium | +| Incomplete observability | Hard to debug failures | 🟢 Low | + +--- + +## Proposed Architecture + +### High-Level Design + +``` +LangGraphBrowserAgent +├── StateGraph (LangGraph) +│ ├── planning_node: Analyze task, create sub-plan +│ ├── action_node: Execute browser actions (wrapped browser-use) +│ ├── observation_node: Extract results, update state +│ ├── decision_node: Determine next action or completion +│ └── synthesis_node: Aggregate final results +├── SqliteSaver (checkpointing) +│ ├── Auto-checkpoint at critical nodes +│ ├── WAL mode for Windows compatibility +│ └── Multi-checkpoint fallback (last 3) +├── MemoryManager +│ ├── Hot: In-memory (last N messages, token-aware) +│ ├── Warm: SQLite summaries (periodic condensation) +│ └── Cold: Historical episodes (defer vector store) +├── RetryPolicy +│ ├── Error classification (navigation, element, network) +│ ├── Strategy-based retry (different approach per error) +│ └── Exponential backoff with max attempts +└── StreamingAdapter + ├── EventBus integration + ├── Backpressure handling (bounded buffer) + └── Node lifecycle events +``` + +### State Definition + +```python +from typing import TypedDict, Optional +from langchain_core.messages import BaseMessage + +class BrowserAgentState(TypedDict): + """Complete agent state (checkpointed).""" + # Core task info + task: str + task_id: str + session_id: str + + # Conversation history (managed by MemoryManager) + messages: list[BaseMessage] + message_summary: Optional[str] # Condensed history + + # Execution state + current_step: int + max_steps: int + actions_taken: list[dict] # History of actions + + # Browser state (minimal, avoid bloat) + current_url: str + current_page_title: str + browser_context_id: str # Reference, not full context + + # Error tracking + failures: int + consecutive_failures: int + last_error: Optional[str] + + # Control flags + paused: bool + stopped: bool + completed: bool + + # Observability + trace_id: str + start_time: float + checkpoint_count: int +``` + +--- + +## Implementation Plan + +### Phase 1: Foundation (Week 1-2) - MUST HAVE + +**Goal:** MVP with checkpointing and basic workflow + +**Deliverables:** + +- [ ] Feature flag system (`USE_LANGGRAPH_AGENT=false` default) +- [ ] `BrowserAgentState` TypedDict +- [ ] Basic workflow: `START → plan → act → observe → decide → END` +- [ ] SqliteSaver with Windows optimizations (WAL, timeout, backoff) +- [ ] Adapter wrapping browser-use Agent in LangGraph nodes +- [ ] Unit tests for state transitions + +**Technical Details:** + +```python +# config.py +from pydantic import BaseModel + +class BrowserAgentConfig(BaseModel): + use_langgraph: bool = False # Feature flag + checkpoint_enabled: bool = True + checkpoint_db_path: str = "./tmp/checkpoints/browser_agent.db" + checkpoint_timeout: int = 30 # seconds + max_checkpoints_to_keep: int = 3 + +# adapter.py +class BrowserUseAdapter: + """Wraps browser-use Agent methods for LangGraph nodes.""" + + def __init__(self, browser_use_agent): + self.agent = browser_use_agent + + async def plan(self, state: BrowserAgentState) -> dict: + """Planning node: analyze task, create action plan.""" + # Use browser-use agent's planning logic + pass + + async def act(self, state: BrowserAgentState) -> dict: + """Action node: execute browser actions.""" + # Delegate to browser-use agent's step() method + pass + + async def observe(self, state: BrowserAgentState) -> dict: + """Observation node: extract results.""" + pass + + async def decide(self, state: BrowserAgentState) -> str: + """Decision node: determine next action.""" + # Return: "act" | "end" | "error" + pass +``` + +**Windows SQLite Setup:** + +```python +from langgraph.checkpoint.sqlite import SqliteSaver +import sqlite3 + +def create_checkpointer(db_path: str) -> SqliteSaver: + """Create SQLite checkpointer with Windows optimizations.""" + conn = sqlite3.connect( + db_path, + timeout=30.0, # 30s timeout for locks + check_same_thread=False + ) + + # Enable WAL mode for better concurrency + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA synchronous=NORMAL") # Balance safety/speed + conn.execute("PRAGMA cache_size=-64000") # 64MB cache + + return SqliteSaver(conn) +``` + +**Exit Criteria:** + +- ✅ Basic workflow runs end-to-end +- ✅ Checkpoint created after each node +- ✅ Resume from checkpoint works (manual test) +- ✅ Feature flag switches between implementations +- ✅ No regressions in legacy agent + +--- + +### Phase 2: Parity & Testing (Week 2-3) - MUST HAVE + +**Goal:** Achieve feature parity with legacy agent + +**Deliverables:** + +- [ ] All workflow nodes implemented (plan, act, observe, decide) +- [ ] Conditional edges (`should_continue`, `should_retry`) +- [ ] Integration test suite (20+ tasks, legacy vs LangGraph) +- [ ] Checkpoint at critical nodes (planning, synthesis) +- [ ] Basic streaming via EventBus +- [ ] Performance baseline measurements + +**Integration Test Strategy:** + +```python +# tests/test_langgraph_parity.py +import pytest + +TEST_TASKS = [ + "Navigate to google.com and search for 'LangGraph'", + "Find the price of iPhone 15 on apple.com", + "Fill out contact form on example.com", + # ... 17 more real-world tasks +] + +@pytest.mark.asyncio +async def test_parity_success_rate(): + """Verify LangGraph agent matches legacy success rate.""" + legacy_results = await run_legacy_agent(TEST_TASKS) + langgraph_results = await run_langgraph_agent(TEST_TASKS) + + assert langgraph_results.success_rate >= legacy_results.success_rate * 0.95 + +@pytest.mark.asyncio +async def test_parity_output_quality(): + """Verify outputs are equivalent (semantic comparison).""" + # Compare final results, allowing for different action paths + pass + +@pytest.mark.asyncio +async def test_checkpoint_resumability(): + """Test resume from every possible checkpoint.""" + for checkpoint_node in ["plan", "act", "observe", "decide"]: + # Kill process at checkpoint_node, resume, verify completion + pass +``` + +**Streaming Integration:** + +```python +# streaming/adapters.py +from src.web_ui.events.event_bus import get_event_bus, EventType, create_event + +async def stream_node_events(app, state, config): + """Stream LangGraph events through EventBus.""" + event_bus = get_event_bus() + + async for event in app.astream(state, config): + node_name = list(event.keys())[0] + node_data = event[node_name] + + # Publish to EventBus + await event_bus.publish(create_event( + event_type=EventType.WORKFLOW_NODE_START, + session_id=state["session_id"], + data={ + "node": node_name, + "state": node_data, + "timestamp": time.time() + } + )) + + yield event +``` + +**Exit Criteria:** + +- ✅ 95%+ success rate parity with legacy +- ✅ Output quality equivalent (manual review) +- ✅ Resume works from any checkpoint (100% success) +- ✅ Performance within +20% of baseline +- ✅ Streaming events render in UI + +--- + +### Phase 3: Memory Management (Week 3-4) - SHOULD HAVE + +**Goal:** Add smart memory management to prevent context overflow + +**Deliverables:** + +- [ ] Hot memory with token-aware trimming (tiktoken) +- [ ] Configurable limits (max tokens: 8000, max messages: 50) +- [ ] Template-based session summaries (no LLM, cost-free) +- [ ] MemoryManager interface +- [ ] Memory tests (trimming, token counting, context preservation) +- [ ] Integration with planning node + +**Memory Implementation:** + +```python +# memory/manager.py +import tiktoken +from langchain_core.messages import BaseMessage, SystemMessage, HumanMessage + +class MemoryManager: + """Unified memory management interface.""" + + def __init__( + self, + max_tokens: int = 8000, + max_messages: int = 50, + model_name: str = "gpt-4" + ): + self.max_tokens = max_tokens + self.max_messages = max_messages + self.tokenizer = tiktoken.encoding_for_model(model_name) + self.hot_memory: list[BaseMessage] = [] + self.summary: str = "" + + def add_message(self, message: BaseMessage): + """Add message, trim if needed.""" + self.hot_memory.append(message) + + if self._should_trim(): + self._trim_memory() + + def _should_trim(self) -> bool: + """Check if trimming is needed.""" + if len(self.hot_memory) > self.max_messages: + return True + + total_tokens = sum( + len(self.tokenizer.encode(msg.content)) + for msg in self.hot_memory + ) + return total_tokens > self.max_tokens + + def _trim_memory(self): + """Trim oldest messages, preserve system + recent.""" + # Keep system message (first) + system_msgs = [m for m in self.hot_memory if isinstance(m, SystemMessage)] + + # Keep last N messages + recent_msgs = self.hot_memory[-30:] + + # Create summary of trimmed messages + trimmed = self.hot_memory[len(system_msgs):-30] + if trimmed: + self.summary = self._create_summary(trimmed) + + # Update hot memory + self.hot_memory = system_msgs + recent_msgs + + def _create_summary(self, messages: list[BaseMessage]) -> str: + """Create template-based summary (no LLM).""" + action_count = sum(1 for m in messages if "action" in m.content.lower()) + error_count = sum(1 for m in messages if "error" in m.content.lower()) + + return f"Previous context: {len(messages)} messages, {action_count} actions taken, {error_count} errors encountered." + + def get_context(self) -> list[BaseMessage]: + """Get current context for LLM.""" + if self.summary: + # Prepend summary as system message + summary_msg = SystemMessage(content=f"Summary: {self.summary}") + return [summary_msg] + self.hot_memory + return self.hot_memory +``` + +**Exit Criteria:** + +- ✅ Trimming works correctly (preserves important messages) +- ✅ Token counting accurate across models +- ✅ Summary generation fast (<10ms) +- ✅ No loss of critical context +- ✅ Memory tests pass (100% coverage) + +--- + +### Phase 4: Reliability & Error Recovery (Week 4-5) - SHOULD HAVE + +**Goal:** Add intelligent retry and error recovery + +**Deliverables:** + +- [ ] Retry policies (exponential backoff, max attempts) +- [ ] Error classification (navigation, element, network, JS) +- [ ] Strategy-based retry (different approach per error type) +- [ ] Observability integration (AgentTracer spans per node) +- [ ] Performance profiling and optimization +- [ ] Load testing (100 concurrent tasks) + +**Retry Implementation:** + +```python +# retry/policies.py +from enum import Enum +import asyncio + +class BrowserErrorType(Enum): + NAVIGATION_TIMEOUT = "navigation_timeout" + ELEMENT_NOT_FOUND = "element_not_found" + STALE_ELEMENT = "stale_element" + NETWORK_ERROR = "network_error" + JAVASCRIPT_ERROR = "javascript_error" + +class RetryPolicy: + """Context-aware retry strategies.""" + + STRATEGIES = { + BrowserErrorType.NAVIGATION_TIMEOUT: [ + {"action": "increase_timeout", "timeout": 60}, + {"action": "refresh_page"}, + {"action": "new_context"} + ], + BrowserErrorType.ELEMENT_NOT_FOUND: [ + {"action": "wait_longer", "wait": 10}, + {"action": "search_by_text"}, + {"action": "relaxed_selector"} + ], + # ... more strategies + } + + MAX_ATTEMPTS = { + BrowserErrorType.NAVIGATION_TIMEOUT: 3, + BrowserErrorType.ELEMENT_NOT_FOUND: 5, + BrowserErrorType.STALE_ELEMENT: 3, + BrowserErrorType.NETWORK_ERROR: 4, + } + + async def retry_with_backoff( + self, + func, + error_type: BrowserErrorType, + max_attempts: int = None + ): + """Execute function with retry and exponential backoff.""" + max_attempts = max_attempts or self.MAX_ATTEMPTS.get(error_type, 3) + + for attempt in range(max_attempts): + try: + return await func() + except Exception as e: + if attempt >= max_attempts - 1: + raise + + # Get retry strategy + strategy = self._get_strategy(error_type, attempt) + + # Apply strategy + await self._apply_strategy(strategy) + + # Exponential backoff + wait_time = min(2 ** attempt, 30) # Cap at 30s + await asyncio.sleep(wait_time) + + def _get_strategy(self, error_type: BrowserErrorType, attempt: int) -> dict: + """Get retry strategy for error type and attempt.""" + strategies = self.STRATEGIES.get(error_type, []) + if not strategies: + return {"action": "exponential_backoff"} + + idx = min(attempt, len(strategies) - 1) + return strategies[idx] + + async def _apply_strategy(self, strategy: dict): + """Apply retry strategy.""" + action = strategy.get("action") + + if action == "increase_timeout": + self.current_timeout = strategy.get("timeout", 60) + elif action == "wait_longer": + wait = strategy.get("wait", 10) + await asyncio.sleep(wait) + # ... more strategy implementations +``` + +**Observability Integration:** + +```python +# Integration with AgentTracer +from src.web_ui.observability.tracer import AgentTracer +from src.web_ui.observability.trace_models import SpanType + +class LangGraphTracerIntegration: + """Attach tracing to LangGraph nodes.""" + + def __init__(self, session_id: str): + self.tracer = AgentTracer(session_id) + + async def trace_node(self, node_name: str, node_func, state: dict): + """Wrap node execution with tracing.""" + async with self.tracer.span( + name=f"node:{node_name}", + span_type=SpanType.AGENT_NODE, + inputs={"state_keys": list(state.keys())} + ) as span: + result = await node_func(state) + + # Add cost if available + if "cost" in result: + span.metadata["cost_usd"] = result["cost"] + if "tokens" in result: + span.metadata["tokens"] = result["tokens"] + + return result +``` + +**Exit Criteria:** + +- ✅ Retry reduces failure rate by >30% +- ✅ Error classification >90% accurate +- ✅ Strategy switching works correctly +- ✅ Tracing captures all node executions +- ✅ Cost tracking accurate within 5% +- ✅ Load test: 100 concurrent tasks complete + +--- + +### Phase 5: Optimization & Polish (Week 5-6) - COULD HAVE + +**Goal:** Optimize performance and add nice-to-have features + +**Deliverables:** + +- [ ] Optimize checkpoint frequency (smart strategy) +- [ ] LLM-based summarization (optional, configurable) +- [ ] Advanced error recovery +- [ ] Result caching (for idempotent actions) +- [ ] Enhanced telemetry dashboards + +**Checkpoint Optimization:** + +```python +# checkpointing/strategy.py +class CheckpointStrategy: + """Smart checkpointing to minimize I/O.""" + + def __init__(self): + self.last_checkpoint_time = 0 + self.last_checkpoint_action_count = 0 + + def should_checkpoint( + self, + node_name: str, + state: dict, + current_time: float + ) -> bool: + """Determine if checkpoint is needed.""" + + # 1. Critical nodes (always checkpoint) + if node_name in ["planning_node", "synthesis_node"]: + return True + + # 2. Time-based (every 30s) + if current_time - self.last_checkpoint_time > 30: + return True + + # 3. Action-based (every 5 actions) + actions = state.get("actions_taken", []) + if len(actions) - self.last_checkpoint_action_count >= 5: + return True + + # 4. Before expensive operations + if node_name in ["navigate_node", "download_node"]: + return True + + # 5. After errors (for recovery) + if state.get("last_error"): + return True + + return False + + def on_checkpoint(self, state: dict, current_time: float): + """Update tracking after checkpoint.""" + self.last_checkpoint_time = current_time + self.last_checkpoint_action_count = len(state.get("actions_taken", [])) +``` + +**Exit Criteria:** + +- ✅ Checkpoint frequency reduced by 50% (smart strategy) +- ✅ Performance within +10% of baseline (improved from +20%) +- ✅ Optional features configurable via flags +- ✅ Telemetry dashboards functional + +--- + +### Phase 6: Production Readiness (Week 6-8) - NICE TO HAVE + +**Goal:** Prepare for production rollout + +**Deliverables:** + +- [ ] Comprehensive documentation (architecture, API, troubleshooting) +- [ ] Migration guide for users +- [ ] Monitoring alerts and dashboards +- [ ] Performance benchmarks published +- [ ] Gradual rollout plan (10% → 50% → 100%) +- [ ] Rollback procedures documented + +**Exit Criteria:** + +- ✅ Documentation complete and reviewed +- ✅ All tests passing (unit, integration, performance) +- ✅ Production monitoring configured +- ✅ Rollback tested successfully +- ✅ Team trained on new architecture + +--- + +## Technical Specifications + +### File Structure + +``` +src/web_ui/agent/browser_use/ +├── browser_use_agent.py # Legacy (keep as fallback) +├── config.py # NEW: Feature flags +├── langgraph/ # NEW: LangGraph implementation +│ ├── __init__.py +│ ├── agent.py # Main LangGraphBrowserAgent +│ ├── state.py # State TypedDicts +│ ├── nodes/ +│ │ ├── __init__.py +│ │ ├── planning.py # Task analysis +│ │ ├── action.py # Browser actions +│ │ ├── observation.py # Result extraction +│ │ └── decision.py # Next action logic +│ ├── memory/ +│ │ ├── __init__.py +│ │ ├── manager.py # MemoryManager +│ │ ├── hot.py # Hot memory (in-memory) +│ │ └── warm.py # Warm memory (SQLite) +│ ├── checkpointing/ +│ │ ├── __init__.py +│ │ ├── saver.py # SqliteSaver wrapper +│ │ └── strategy.py # Checkpoint policies +│ ├── retry/ +│ │ ├── __init__.py +│ │ ├── policies.py # Retry strategies +│ │ ├── backoff.py # Backoff algorithms +│ │ └── classifiers.py # Error classification +│ └── streaming/ +│ ├── __init__.py +│ ├── events.py # Event schemas +│ └── adapters.py # EventBus integration +└── utils/ + ├── __init__.py + ├── adapter.py # browser-use wrapper + └── validators.py # State validation + +tests/ +├── test_langgraph_agent.py # Unit tests +├── test_parity.py # Integration tests +├── test_checkpointing.py # Checkpoint tests +├── test_memory.py # Memory tests +└── test_performance.py # Performance benchmarks +``` + +### Dependencies + +```toml +[dependencies] +# Already installed +langgraph = ">=0.3.34" +langchain-community = ">=0.3.0" +browser-use = "==0.1.48" # Version pin + +# New dependencies +tiktoken = ">=0.7.0" # Token counting +sqlalchemy = ">=2.0.0" # For SqliteSaver + +# Optional (defer) +chromadb = ">=0.5.0" # Vector store (Phase 6+) +``` + +### Configuration + +```python +# .env additions +USE_LANGGRAPH_AGENT=false # Feature flag (default OFF) +CHECKPOINT_ENABLED=true +CHECKPOINT_DB_PATH=./tmp/checkpoints/browser_agent.db +CHECKPOINT_TIMEOUT=30 +MAX_MEMORY_TOKENS=8000 +MAX_MEMORY_MESSAGES=50 +RETRY_MAX_ATTEMPTS=3 +``` + +--- + +## Risk Management + +### High-Risk Items + +| Risk | Impact | Probability | Mitigation | +|------|--------|-------------|------------| +| browser-use API changes | 🔴 High | 🟡 Medium | Version pin, integration tests, monitor releases | +| SQLite corruption | 🔴 High | 🟢 Low | Checksums, multi-checkpoint backup, recovery logic | +| Memory leaks | 🔴 High | 🟡 Medium | Max session duration, periodic GC, leak tests | +| Performance regression | 🟡 Medium | 🟡 Medium | Early profiling, opt-out flag, smart checkpointing | + +### Mitigation Strategies + +1. **Version Pinning:** + - Pin `browser-use==0.1.48` in `pyproject.toml` + - Add tests for browser-use API surface + - Monitor upstream releases, review changes before upgrading + +2. **Checkpoint Integrity:** + - CRC32 checksums on all checkpoints + - Keep last 3 checkpoints (multi-level fallback) + - Background validation every 5 minutes + - Auto-recovery on corruption detection + +3. **Memory Management:** + - Hard limit: 30-minute max session duration + - Periodic GC every 50 actions + - Browser context pooling (max 3 contexts) + - Leak detection in integration tests + +4. **Performance:** + - Profile from day 1 + - Add `DISABLE_CHECKPOINTING` flag for perf mode + - Optimize checkpoint frequency based on data + - Target: +10% overhead (stretch: +20% acceptable) + +--- + +## Success Criteria + +### Must-Have (Week 1-4) + +- ✅ Feature flag working (easy toggle) +- ✅ Checkpointing working (100% resumability) +- ✅ 95%+ parity with legacy (success rate) +- ✅ Performance: +20% overhead or less +- ✅ Streaming: Events render in UI +- ✅ Memory: Token-aware trimming works +- ✅ Tests: Integration tests pass + +### Should-Have (Week 4-6) + +- ✅ Retry: 30% failure reduction +- ✅ Observability: Full tracing +- ✅ Performance: +10% overhead (optimized) +- ✅ Load test: 100 concurrent tasks pass + +### Nice-to-Have (Week 6-8) + +- ✅ Documentation complete +- ✅ Production monitoring configured +- ✅ Gradual rollout plan ready +- ✅ Optional features (LLM summarization, caching) + +--- + +## Performance Benchmarks + +| Metric | Baseline (Legacy) | Target (LangGraph) | Red Flag | +|--------|-------------------|-------------------|----------| +| Avg step latency | 2-5s | 2-6s | >7s | +| Memory per session | 50-100MB | 100-150MB | >200MB | +| Checkpoint write | N/A | <100ms | >500ms | +| Resume time | N/A | <500ms | >2s | +| Stream latency | 100ms | 50-100ms | >300ms | +| Token usage | Baseline | +0-10% | >+30% | +| Success rate | 85-90% | 85-90% | <80% | + +--- + +## Testing Strategy + +### Test Pyramid + +``` + /\ + / \ E2E Tests (10 tests) + / \ Integration Tests (50 tests) + /______\ Unit Tests (200+ tests) +``` + +### Test Categories + +1. **Unit Tests (200+):** + - State transitions + - Memory trimming + - Retry logic + - Error classification + - Checkpoint serialization + +2. **Integration Tests (50):** + - Legacy vs LangGraph parity (20 tasks) + - Checkpoint resumability (10 scenarios) + - Memory management (10 scenarios) + - Error recovery (10 scenarios) + +3. **Performance Tests:** + - Latency benchmarks + - Memory leak detection + - Checkpoint overhead + - Streaming throughput + +4. **Load Tests:** + - 100 concurrent tasks + - SQLite lock contention + - Memory pressure scenarios + +--- + +## Migration Path + +### Gradual Rollout Strategy + +**Week 1-2: Internal Alpha** + +- Feature flag: ON for dev team only +- Manual testing with real tasks +- Fix critical bugs + +**Week 3-4: Internal Beta** + +- Feature flag: ON for staging environment +- Automated integration tests running +- Performance profiling + +**Week 5-6: Limited Production (10%)** + +- Feature flag: 10% of production tasks +- Monitor metrics closely +- Ready to rollback + +**Week 7: Expanded Production (50%)** + +- Feature flag: 50% of production tasks +- Gather user feedback +- Fine-tune performance + +**Week 8: Full Production (100%)** + +- Feature flag: 100% of production tasks +- Legacy agent as fallback (keep for 1 month) +- Monitor for regressions + +### Rollback Procedures + +If issues arise: + +1. **Immediate Rollback:** + + ```bash + # Set feature flag to OFF + export USE_LANGGRAPH_AGENT=false + # Restart application + ``` + +2. **Per-User Rollback:** + + ```python + # Allow users to opt-out + if user.preferences.get("use_legacy_agent"): + agent = BrowserUseAgent(...) + else: + agent = LangGraphBrowserAgent(...) + ``` + +3. **Automatic Fallback:** + + ```python + try: + result = await langgraph_agent.run(task) + except LangGraphError: + logger.warning("LangGraph failed, falling back to legacy") + result = await legacy_agent.run(task) + ``` + +--- + +## Decision Log + +| Decision | Rationale | Trade-off | Date | +|----------|-----------|-----------|------| +| Gradual migration | Minimize risk | Longer timeline | Oct 22 | +| Wrapper pattern | Maintain compatibility | Extra layer | Oct 22 | +| Defer vector store | Unclear benefit | Less sophisticated | Oct 22 | +| Template summaries | Cost/latency | Less accurate | Oct 22 | +| SQLite checkpoints | Simple, proven | Not distributed | Oct 22 | +| Feature flag OFF default | Safety first | Manual enablement | Oct 22 | + +--- + +## Next Steps + +### Immediate Actions (This Week) + +1. **Review & Approve Plan** + - [ ] Team review meeting + - [ ] Get stakeholder approval + - [ ] Finalize timeline + +2. **Setup Development Environment** + - [ ] Create feature branch: `feature/langgraph-migration` + - [ ] Setup checkpoint database directory + - [ ] Configure feature flags + +3. **Start Phase 1 Implementation** + - [ ] Create `config.py` with feature flags + - [ ] Define `BrowserAgentState` TypedDict + - [ ] Implement basic adapter wrapper + - [ ] Setup SqliteSaver with Windows optimizations + +### Week 1 Goals + +- Complete Phase 1 deliverables +- First checkpoint working +- Basic workflow (plan → act → end) functional +- Feature flag tested + +--- + +## Resources & References + +### Documentation + +- [LangGraph Docs](https://langchain-ai.github.io/langgraph/) +- [SqliteSaver API](https://langchain-ai.github.io/langgraph/reference/checkpoints/) +- [browser-use Docs](https://github.com/browser-use/browser-use) + +### Internal References + +- `CLAUDE.md` - Project overview and standards +- `src/web_ui/agent/deep_research/deep_research_agent.py` - LangGraph example +- `src/web_ui/events/event_bus.py` - Event system +- `src/web_ui/observability/tracer.py` - Tracing infrastructure + +### Team Contacts + +- **Project Lead:** Shaun (@savagelysubtle) +- **Architecture Review:** Team +- **Testing:** QA Team +- **DevOps:** Deployment Team + +--- + +## Appendix: Code Examples + +### Complete LangGraphBrowserAgent Example + +```python +from langgraph.graph import StateGraph +from langgraph.checkpoint.sqlite import SqliteSaver +from typing import TypedDict, Optional +import uuid + +class BrowserAgentState(TypedDict): + task: str + session_id: str + messages: list + current_step: int + max_steps: int + completed: bool + +class LangGraphBrowserAgent: + """LangGraph-based browser agent.""" + + def __init__(self, llm, browser, controller, config): + self.llm = llm + self.browser = browser + self.controller = controller + self.config = config + self.graph = self._build_graph() + + def _build_graph(self): + """Build LangGraph workflow.""" + workflow = StateGraph(BrowserAgentState) + + # Add nodes + workflow.add_node("plan", self.plan_node) + workflow.add_node("act", self.act_node) + workflow.add_node("observe", self.observe_node) + workflow.add_node("decide", self.decide_node) + + # Setup edges + workflow.set_entry_point("plan") + workflow.add_edge("plan", "act") + workflow.add_edge("act", "observe") + workflow.add_edge("observe", "decide") + + # Conditional edge + workflow.add_conditional_edges( + "decide", + self.should_continue, + { + "continue": "act", + "end": END + } + ) + + # Compile with checkpointing + checkpointer = create_checkpointer(self.config.checkpoint_db_path) + return workflow.compile(checkpointer=checkpointer) + + async def plan_node(self, state: BrowserAgentState) -> dict: + """Planning node.""" + # Implementation + return {"current_step": state["current_step"] + 1} + + async def act_node(self, state: BrowserAgentState) -> dict: + """Action node.""" + # Implementation + return {} + + async def observe_node(self, state: BrowserAgentState) -> dict: + """Observation node.""" + # Implementation + return {} + + async def decide_node(self, state: BrowserAgentState) -> dict: + """Decision node.""" + # Implementation + return {} + + def should_continue(self, state: BrowserAgentState) -> str: + """Conditional edge logic.""" + if state["completed"] or state["current_step"] >= state["max_steps"]: + return "end" + return "continue" + + async def run(self, task: str, max_steps: int = 100): + """Run agent with checkpointing.""" + initial_state = { + "task": task, + "session_id": str(uuid.uuid4()), + "messages": [], + "current_step": 0, + "max_steps": max_steps, + "completed": False + } + + config = { + "configurable": { + "thread_id": initial_state["session_id"] + } + } + + # Stream execution + async for event in self.graph.astream(initial_state, config): + yield event + + # Get final state + final = await self.graph.aget_state(config) + return final +``` + +--- + +**End of Plan** + +*This is a living document. Update as implementation progresses.* diff --git a/docs/LLM_DROPDOWN_FIX_SUMMARY.md b/docs/LLM_DROPDOWN_FIX_SUMMARY.md new file mode 100644 index 00000000..e69de29b diff --git a/docs/WORKFLOW_GRAPH_REVIEW.md b/docs/WORKFLOW_GRAPH_REVIEW.md new file mode 100644 index 00000000..38c2b709 --- /dev/null +++ b/docs/WORKFLOW_GRAPH_REVIEW.md @@ -0,0 +1,566 @@ +# WorkflowGraphBuilder Review & Integration Plan + +**Status:** CRITICAL - Missing from Migration Plan +**Impact:** HIGH - Affects UI visualization +**Date:** October 22, 2025 + +--- + +## Executive Summary + +`workflow_graph.py` is **essential infrastructure** that was underspecified in the LangGraph migration plan. It provides UI visualization for agent execution and needs to be **extended, not replaced** to support LangGraph nodes while maintaining backward compatibility with the legacy agent. + +--- + +## Current State + +### What It Does + +`WorkflowGraphBuilder` creates visual workflow graphs for Gradio UI with: + +```python +NodeType: +- START: Beginning of execution +- THINKING: LLM reasoning +- ACTION: Browser actions +- RESULT: Action results +- ERROR: Failures +- END: Completion + +NodeStatus: +- PENDING, RUNNING, COMPLETED, ERROR, SKIPPED +``` + +**Key Features:** + +- ✅ Tracks node lifecycle (start_time, end_time, duration) +- ✅ Creates edges between nodes (animated for active) +- ✅ Sanitizes sensitive params (passwords, tokens) +- ✅ Icons for different action types +- ✅ JSON serialization for Gradio + +**Integration Points:** + +- Used by `workflow_visualizer.py` (Gradio component) +- Currently supports legacy `BrowserUseAgent` +- No integration with EventBus yet +- No LangGraph-specific node types + +--- + +## Gap Analysis + +### What's Missing for LangGraph + +1. **New Node Types:** + - No `PLANNING` node type (LangGraph planning_node) + - No `OBSERVATION` node type (LangGraph observation_node) + - No `DECISION` node type (LangGraph decision_node) + - No `CHECKPOINT` node type (show checkpoint events) + +2. **EventBus Integration:** + - Not subscribed to `EventType.WORKFLOW_NODE_START/END` + - Can't auto-update from streaming events + - Requires manual node addition + +3. **LangGraph-Specific Features:** + - No support for conditional edges (different from error edges) + - No checkpoint visualization + - No state metadata display + - No retry visualization (show retry attempts) + +4. **Performance:** + - No node deduplication (could create duplicate nodes in loops) + - No depth limit (infinite loop protection) + - Loads entire graph in memory + +--- + +## Proposed Changes + +### Phase 1: Extend Node Types (Week 2) + +Add LangGraph-specific node types: + +```python +# workflow_graph.py + +class NodeType(str, Enum): + """Types of workflow nodes.""" + + # Existing + START = "start" + THINKING = "thinking" + ACTION = "action" + RESULT = "result" + ERROR = "error" + END = "end" + + # NEW: LangGraph nodes + PLANNING = "planning" # 🎯 + OBSERVATION = "observation" # 👁️ + DECISION = "decision" # 🤔 + CHECKPOINT = "checkpoint" # 💾 + RETRY = "retry" # 🔄 +``` + +**Add Node Creation Methods:** + +```python +def add_planning_node(self, parent_id: str, plan: str) -> str: + """Add a planning node (LangGraph).""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = parent_node.position["y"] + self.vertical_spacing if parent_node else 0 + + node = WorkflowNode( + id=node_id, + type=NodeType.PLANNING, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": "Planning", + "plan": plan[:200] + "..." if len(plan) > 200 else plan, + "full_plan": plan, + "icon": "🎯", + }, + status=NodeStatus.RUNNING, + start_time=time.time(), + ) + + self.nodes.append(node) + self._add_edge(parent_id, node_id) + return node_id + +def add_observation_node(self, parent_id: str, observation: dict) -> str: + """Add an observation node (LangGraph).""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = parent_node.position["y"] + self.vertical_spacing if parent_node else 0 + + node = WorkflowNode( + id=node_id, + type=NodeType.OBSERVATION, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": "Observation", + "observation": str(observation)[:200], + "full_observation": observation, + "icon": "👁️", + }, + status=NodeStatus.COMPLETED, + start_time=time.time(), + end_time=time.time(), + ) + + self.nodes.append(node) + self._add_edge(parent_id, node_id) + return node_id + +def add_decision_node(self, parent_id: str, decision: str, reasoning: str = "") -> str: + """Add a decision node (LangGraph).""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = parent_node.position["y"] + self.vertical_spacing if parent_node else 0 + + node = WorkflowNode( + id=node_id, + type=NodeType.DECISION, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": f"Decision: {decision}", + "decision": decision, + "reasoning": reasoning, + "icon": "🤔", + }, + status=NodeStatus.COMPLETED, + start_time=time.time(), + end_time=time.time(), + ) + + self.nodes.append(node) + self._add_edge(parent_id, node_id, label=decision) + return node_id + +def add_checkpoint_node(self, parent_id: str, checkpoint_id: str) -> str: + """Add a checkpoint node (shows state persistence).""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = parent_node.position["y"] + self.vertical_spacing if parent_node else 0 + + node = WorkflowNode( + id=node_id, + type=NodeType.CHECKPOINT, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": "Checkpoint", + "checkpoint_id": checkpoint_id, + "icon": "💾", + }, + status=NodeStatus.COMPLETED, + start_time=time.time(), + end_time=time.time(), + ) + + self.nodes.append(node) + self._add_edge(parent_id, node_id, animated=False) + return node_id + +def add_retry_node(self, parent_id: str, attempt: int, strategy: str) -> str: + """Add a retry node (shows retry attempts).""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = parent_node.position["y"] + self.vertical_spacing if parent_node else 0 + + node = WorkflowNode( + id=node_id, + type=NodeType.RETRY, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": f"Retry #{attempt}", + "attempt": attempt, + "strategy": strategy, + "icon": "🔄", + }, + status=NodeStatus.RUNNING, + start_time=time.time(), + ) + + self.nodes.append(node) + self._add_edge(parent_id, node_id, label=f"Retry {strategy}") + return node_id + +def _add_edge(self, parent_id: str, child_id: str, label: str = None, animated: bool = True): + """Helper to add edge between nodes.""" + edge = WorkflowEdge( + id=f"edge_{parent_id}_{child_id}", + source=parent_id, + target=child_id, + animated=animated, + label=label + ) + self.edges.append(edge) +``` + +--- + +### Phase 2: EventBus Integration (Week 2-3) + +Create adapter to convert EventBus events to workflow graph nodes: + +```python +# utils/workflow_event_adapter.py + +from src.web_ui.events.event_bus import EventType, Event +from src.web_ui.utils.workflow_graph import WorkflowGraphBuilder + +class WorkflowEventAdapter: + """Adapter to convert EventBus events to workflow graph nodes.""" + + def __init__(self, graph_builder: WorkflowGraphBuilder): + self.graph = graph_builder + self.node_map: dict[str, str] = {} # Maps event IDs to node IDs + self.current_parent: str = None + + async def handle_event(self, event: Event): + """Handle incoming EventBus event.""" + + if event.event_type == EventType.AGENT_START: + # Create start node + task = event.data.get("task", "Unknown task") + node_id = self.graph.add_start_node(task) + self.current_parent = node_id + self.node_map[event.data.get("session_id")] = node_id + + elif event.event_type == EventType.WORKFLOW_NODE_START: + # Map LangGraph node to workflow node + node_name = event.data.get("node") + + if node_name == "planning_node": + plan = event.data.get("state", {}).get("plan", "") + node_id = self.graph.add_planning_node(self.current_parent, plan) + + elif node_name == "action_node": + action = event.data.get("state", {}).get("action", {}) + node_id = self.graph.add_action_node( + self.current_parent, + action.get("name", "unknown"), + action.get("params", {}), + status=NodeStatus.RUNNING + ) + + elif node_name == "observation_node": + observation = event.data.get("state", {}).get("observation", {}) + node_id = self.graph.add_observation_node(self.current_parent, observation) + + elif node_name == "decision_node": + decision = event.data.get("state", {}).get("decision", "continue") + reasoning = event.data.get("state", {}).get("reasoning", "") + node_id = self.graph.add_decision_node(self.current_parent, decision, reasoning) + + else: + # Generic node + node_id = self.graph.add_thinking_node( + self.current_parent, + f"Node: {node_name}", + model_name=event.data.get("model") + ) + + self.current_parent = node_id + self.node_map[event.data.get("node_id", node_name)] = node_id + + elif event.event_type == EventType.WORKFLOW_NODE_COMPLETE: + # Update node status to completed + node_name = event.data.get("node") + node_id = self.node_map.get(event.data.get("node_id", node_name)) + + if node_id: + duration = event.data.get("duration_ms") + result = event.data.get("state", {}).get("result") + self.graph.update_node_status( + node_id, + NodeStatus.COMPLETED, + duration=duration, + result=result + ) + + elif event.event_type == EventType.AGENT_COMPLETE: + # Add end node + final_result = event.data.get("result", "Task completed") + self.graph.add_end_node(self.current_parent, final_result) + + elif event.event_type == EventType.AGENT_ERROR: + # Add error node + error = event.data.get("error", "Unknown error") + self.graph.add_error_node(self.current_parent, error) + + # Return updated graph + return self.graph.to_dict() +``` + +**Usage in workflow_visualizer.py:** + +```python +# webui/components/workflow_visualizer.py + +from src.web_ui.utils.workflow_graph import WorkflowGraphBuilder +from src.web_ui.utils.workflow_event_adapter import WorkflowEventAdapter +from src.web_ui.events.event_bus import get_event_bus, EventType + +class WorkflowVisualizer: + """Real-time workflow visualization component.""" + + def __init__(self): + self.graph = WorkflowGraphBuilder() + self.adapter = WorkflowEventAdapter(self.graph) + self.event_bus = get_event_bus() + + async def start_listening(self, session_id: str): + """Start listening to events for a session.""" + + # Subscribe to relevant events + event_types = [ + EventType.AGENT_START, + EventType.WORKFLOW_NODE_START, + EventType.WORKFLOW_NODE_COMPLETE, + EventType.AGENT_COMPLETE, + EventType.AGENT_ERROR, + ] + + for event_type in event_types: + await self.event_bus.subscribe( + event_type, + lambda e: self.adapter.handle_event(e) if e.session_id == session_id else None + ) + + def get_graph_data(self) -> dict: + """Get current graph data for rendering.""" + return self.graph.to_dict() +``` + +--- + +### Phase 3: Enhanced Features (Week 3-4) + +**1. Add Conditional Edges:** + +```python +# In WorkflowEdge +@dataclass +class WorkflowEdge: + """A connection between workflow nodes.""" + + id: str + source: str + target: str + animated: bool = False + label: str | None = None + edge_type: str = "normal" # NEW: "normal", "conditional", "retry", "error" + condition: str | None = None # NEW: For conditional edges + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary for JSON serialization.""" + result = { + "id": self.id, + "source": self.source, + "target": self.target, + "type": self.edge_type, # For UI styling + } + + if self.animated: + result["animated"] = True + if self.label: + result["label"] = self.label + if self.condition: + result["condition"] = self.condition + + return result +``` + +**2. Add State Metadata Display:** + +```python +def add_state_snapshot(self, node_id: str, state: dict): + """Attach state snapshot to node (for debugging).""" + node = self._get_node_by_id(node_id) + if node: + # Sanitize state (remove large objects) + sanitized_state = { + k: v for k, v in state.items() + if k not in ["browser_context", "page_html"] # Skip large objects + } + node.data["state_snapshot"] = sanitized_state +``` + +**3. Add Depth Limit (Infinite Loop Protection):** + +```python +class WorkflowGraphBuilder: + """Builds workflow graph data from agent execution.""" + + def __init__(self, max_depth: int = 100): + self.nodes: list[WorkflowNode] = [] + self.edges: list[WorkflowEdge] = [] + self.node_counter = 0 + self.current_depth = 0 + self.horizontal_offset = 250 + self.vertical_spacing = 120 + self.max_depth = max_depth # NEW: Prevent infinite loops + + def _check_depth_limit(self) -> bool: + """Check if we've hit max depth.""" + if self.current_depth >= self.max_depth: + logger.warning(f"Reached max graph depth: {self.max_depth}") + return True + return False + + def add_action_node(self, parent_id: str, action: str, params: dict, status: NodeStatus = NodeStatus.PENDING) -> str: + """Add an action node.""" + if self._check_depth_limit(): + return self._add_truncation_node(parent_id) + + # ... rest of implementation + + def _add_truncation_node(self, parent_id: str) -> str: + """Add node indicating graph was truncated.""" + node_id = self._generate_node_id() + + node = WorkflowNode( + id=node_id, + type=NodeType.END, + position={"x": self.horizontal_offset, "y": self.current_depth * self.vertical_spacing}, + data={ + "label": "Graph Truncated", + "message": f"Max depth ({self.max_depth}) reached. Graph truncated for performance.", + "icon": "⚠️" + }, + status=NodeStatus.SKIPPED, + ) + + self.nodes.append(node) + self._add_edge(parent_id, node_id) + return node_id +``` + +--- + +## Integration Checklist + +### Week 2 (Phase 2 of Migration) + +- [ ] Add new node types (PLANNING, OBSERVATION, DECISION, CHECKPOINT, RETRY) +- [ ] Add node creation methods for each new type +- [ ] Create `WorkflowEventAdapter` class +- [ ] Update `workflow_visualizer.py` to use adapter +- [ ] Test with both legacy and LangGraph agents + +### Week 3 (Phase 3 of Migration) + +- [ ] Add conditional edge support +- [ ] Add state snapshot capability +- [ ] Add depth limit protection +- [ ] Enhanced styling for different edge types +- [ ] Add checkpoint visualization + +### Week 4 (Testing) + +- [ ] Integration tests (legacy vs LangGraph visualization) +- [ ] Performance tests (large graphs with 100+ nodes) +- [ ] UI tests (verify Gradio rendering) + +--- + +## Risk Assessment + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Breaking changes in existing UI | 🔴 High | Maintain backward compatibility, feature flag | +| Large graphs slow down UI | 🟡 Medium | Add depth limit, lazy loading, virtualization | +| EventBus subscription leaks | 🟡 Medium | Proper cleanup on session end | +| Graph state diverges from actual | 🟢 Low | Single source of truth (EventBus) | + +--- + +## Recommendations + +### MUST DO + +1. ✅ **Extend `WorkflowGraphBuilder`** with LangGraph node types (Week 2) +2. ✅ **Create `WorkflowEventAdapter`** for EventBus integration (Week 2) +3. ✅ **Add depth limit** to prevent UI crashes (Week 3) +4. ✅ **Test with both agent types** to ensure compatibility (Week 4) + +### SHOULD DO + +- Add conditional edge visualization (better UX) +- Add checkpoint nodes (helps debugging) +- Add retry visualization (shows recovery) +- State snapshot display (debugging aid) + +### NICE TO HAVE + +- Graph export to PNG/SVG +- Timeline view (alternative visualization) +- Graph diff (compare two executions) +- Interactive node details panel + +--- + +## Conclusion + +**`WorkflowGraphBuilder` is CRITICAL infrastructure** that must be updated for LangGraph migration. The good news: + +✅ **Architecture is sound** - just needs extension, not replacement +✅ **Backward compatible** - can support both legacy and LangGraph +✅ **EventBus integration** - natural fit for streaming updates +⚠️ **Missing from plan** - needs to be added to Phase 2 deliverables + +**Action:** Update migration plan to include `WorkflowGraphBuilder` extension in Phase 2 (Week 2). + +--- + +**End of Review** diff --git a/mcp.example.json b/mcp.example.json new file mode 100644 index 00000000..77fc66eb --- /dev/null +++ b/mcp.example.json @@ -0,0 +1,129 @@ +{ + "mcpServers": { + "filesystem": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-filesystem", + "/path/to/allowed/directory" + ], + "transport": "stdio" + }, + "fetch": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-fetch"], + "transport": "stdio" + }, + "puppeteer": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-puppeteer"], + "transport": "stdio" + }, + "brave-search": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-brave-search"], + "env": { + "BRAVE_API_KEY": "your_brave_api_key_here" + }, + "transport": "stdio" + }, + "github": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-github"], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "your_github_token_here" + }, + "transport": "stdio" + }, + "postgres": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-postgres", + "postgresql://localhost/mydb" + ], + "transport": "stdio" + }, + "sqlite": { + "command": "npx", + "args": [ + "-y", + "@modelcontextprotocol/server-sqlite", + "--db-path", + "/path/to/database.db" + ], + "transport": "stdio" + }, + "git": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-git"], + "transport": "stdio" + }, + "google-maps": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-google-maps"], + "env": { + "GOOGLE_MAPS_API_KEY": "your_google_maps_api_key_here" + }, + "transport": "stdio" + }, + "slack": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-slack"], + "env": { + "SLACK_BOT_TOKEN": "xoxb-your-token-here", + "SLACK_TEAM_ID": "your-team-id" + }, + "transport": "stdio" + }, + "sentry": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-sentry"], + "env": { + "SENTRY_AUTH_TOKEN": "your_sentry_token_here", + "SENTRY_ORG": "your-org-slug" + }, + "transport": "stdio" + }, + "memory": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-memory"], + "transport": "stdio" + }, + "sequential-thinking": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-sequential-thinking"], + "transport": "stdio" + }, + "everything": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-everything"], + "transport": "stdio" + }, + "aws-kb-retrieval-server": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-aws-kb-retrieval"], + "env": { + "AWS_ACCESS_KEY_ID": "your_aws_access_key", + "AWS_SECRET_ACCESS_KEY": "your_aws_secret_key", + "AWS_REGION": "us-east-1" + }, + "transport": "stdio" + }, + "playwright": { + "command": "npx", + "args": ["-y", "@executeautomation/playwright-mcp-server"], + "transport": "stdio" + }, + "desktop-commander": { + "command": "npx", + "args": ["-y", "desktop-commander"], + "transport": "stdio" + }, + "youtube-transcript": { + "command": "npx", + "args": ["-y", "@kimtaeyoon83/mcp-server-youtube-transcript"], + "transport": "stdio" + } + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..7067533e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,108 @@ +[project] +authors = [ + { name = "Browser Use Team", email = "contact@browser-use.com" }, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: Microsoft :: Windows", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Scientific/Engineering :: Artificial Intelligence", +] +description = "WebUI for browser-use with expanded LLM support and custom browser integration (Windows-optimized with UV)" +keywords = [ "browser-use", "ai-agent", "web-ui", "automation", "llm", "windows", "uv" ] +license = { text = "MIT" } +maintainers = [ + { name = "Shaun", email = "simpleflowworks@gmail.com" }, +] +name = "web-ui" +readme = "README.md" +requires-python = ">=3.11,<3.15" +version = "0.1.0" + +dependencies = [ + "browser-use==0.1.48", + "pyperclip>=1.9.0", + "gradio>=5.27.0", + "json-repair>=0.25.0", + "langchain-mistralai>=0.2.4", + "MainContentExtractor>=0.0.4", + "langchain-ibm>=0.3.10", + "langchain_mcp_adapters>=0.0.9", + "langgraph>=0.3.34", + "langchain-community>=0.3.0", + "playwright>=1.40.0", + "python-dotenv>=1.0.0", +] + + [project.urls] + "Bug Tracker" = "https://github.com/browser-use/web-ui/issues" + Documentation = "https://docs.browser-use.com" + Homepage = "https://github.com/browser-use/web-ui" + Repository = "https://github.com/browser-use/web-ui" + + [project.scripts] + webui = "webui:main" + +[dependency-groups] +dev = [ + "ruff>=0.8.0", + "pytest>=8.0.0", + "pytest-asyncio>=0.23.0", + "ty>=0.0.1a23", +] + +[tool.setuptools] +# Package discovery for UV build backend +packages = { find = { where = [ "src" ], include = [ "web_ui*" ] } } + +[build-system] +build-backend = "uv_build" +requires = [ "uv_build>=0.9.4,<0.10.0" ] #!! AI LEAVE THIS IS CORRECT + +[tool.ruff] +line-length = 100 +target-version = "py314" + + [tool.ruff.lint] + ignore = [ + "E501", # line too long (handled by formatter) + "B008", # do not perform function calls in argument defaults + "C901", # too complex + ] + select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade + ] + + [tool.ruff.format] + docstring-code-format = true + indent-style = "space" + quote-style = "double" + + [tool.ruff.lint.per-file-ignores] + "__init__.py" = [ "F401" ] + +[tool.pytest.ini_options] +asyncio_mode = "auto" +python_classes = [ "Test*" ] +python_files = [ "test_*.py" ] +python_functions = [ "test_*" ] +testpaths = [ "tests" ] + +[tool.ty] +# ty configuration - Astral's fast Rust-based type checker (alpha version) +# Note: ty is still in alpha (0.0.1a23) - expect potential bugs and missing features +# Python 3.14t support will be configured via runtime environment diff --git a/requirements.txt b/requirements.txt index cdda0d11..af7b73ce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,12 @@ -browser-use -langchain-google-genai -pyperclip -gradio -langchain-ollama - +browser-use>=0.1.48 +pyperclip>=1.9.0 +gradio>=5.27.0 +json-repair>=0.25.0 +langchain-mistralai>=0.2.4 +MainContentExtractor>=0.0.4 +langchain-ibm>=0.3.10 +langchain_mcp_adapters>=0.0.9 +langgraph>=0.3.34 +langchain-community>=0.3.0 +playwright>=1.40.0 +python-dotenv>=1.0.0 diff --git a/src/__init__.py b/src/__init__.py deleted file mode 100644 index 93fbe7f8..00000000 --- a/src/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: __init__.py.py diff --git a/src/agent/__init__.py b/src/agent/__init__.py deleted file mode 100644 index 93fbe7f8..00000000 --- a/src/agent/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: __init__.py.py diff --git a/src/agent/custom_agent.py b/src/agent/custom_agent.py deleted file mode 100644 index 027a450f..00000000 --- a/src/agent/custom_agent.py +++ /dev/null @@ -1,268 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: custom_agent.py - -import asyncio -import base64 -import io -import json -import logging -import os -import pdb -import textwrap -import time -import uuid -from io import BytesIO -from pathlib import Path -from typing import Any, Optional, Type, TypeVar - -from dotenv import load_dotenv -from langchain_core.language_models.chat_models import BaseChatModel -from langchain_core.messages import ( - BaseMessage, - SystemMessage, -) -from openai import RateLimitError -from PIL import Image, ImageDraw, ImageFont -from pydantic import BaseModel, ValidationError - -from browser_use.agent.message_manager.service import MessageManager -from browser_use.agent.prompts import AgentMessagePrompt, SystemPrompt -from browser_use.agent.service import Agent -from browser_use.agent.views import ( - ActionResult, - AgentError, - AgentHistory, - AgentHistoryList, - AgentOutput, - AgentStepInfo, -) -from browser_use.browser.browser import Browser -from browser_use.browser.context import BrowserContext -from browser_use.browser.views import BrowserState, BrowserStateHistory -from browser_use.controller.registry.views import ActionModel -from browser_use.controller.service import Controller -from browser_use.dom.history_tree_processor.service import ( - DOMHistoryElement, - HistoryTreeProcessor, -) -from browser_use.telemetry.service import ProductTelemetry -from browser_use.telemetry.views import ( - AgentEndTelemetryEvent, - AgentRunTelemetryEvent, - AgentStepErrorTelemetryEvent, -) -from browser_use.utils import time_execution_async - -from .custom_views import CustomAgentOutput, CustomAgentStepInfo -from .custom_massage_manager import CustomMassageManager - -logger = logging.getLogger(__name__) - - -class CustomAgent(Agent): - - def __init__( - self, - task: str, - llm: BaseChatModel, - add_infos: str = '', - browser: Browser | None = None, - browser_context: BrowserContext | None = None, - controller: Controller = Controller(), - use_vision: bool = True, - save_conversation_path: Optional[str] = None, - max_failures: int = 5, - retry_delay: int = 10, - system_prompt_class: Type[SystemPrompt] = SystemPrompt, - max_input_tokens: int = 128000, - validate_output: bool = False, - include_attributes: list[str] = [ - 'title', - 'type', - 'name', - 'role', - 'tabindex', - 'aria-label', - 'placeholder', - 'value', - 'alt', - 'aria-expanded', - ], - max_error_length: int = 400, - max_actions_per_step: int = 10, - ): - super().__init__(task, llm, browser, browser_context, controller, use_vision, save_conversation_path, - max_failures, retry_delay, system_prompt_class, max_input_tokens, validate_output, - include_attributes, max_error_length, max_actions_per_step) - self.add_infos = add_infos - self.message_manager = CustomMassageManager( - llm=self.llm, - task=self.task, - action_descriptions=self.controller.registry.get_prompt_description(), - system_prompt_class=self.system_prompt_class, - max_input_tokens=self.max_input_tokens, - include_attributes=self.include_attributes, - max_error_length=self.max_error_length, - max_actions_per_step=self.max_actions_per_step, - ) - - def _setup_action_models(self) -> None: - """Setup dynamic action models from controller's registry""" - # Get the dynamic action model from controller's registry - self.ActionModel = self.controller.registry.create_action_model() - # Create output model with the dynamic actions - self.AgentOutput = CustomAgentOutput.type_with_custom_actions(self.ActionModel) - - def _log_response(self, response: CustomAgentOutput) -> None: - """Log the model's response""" - if 'Success' in response.current_state.prev_action_evaluation: - emoji = '✅' - elif 'Failed' in response.current_state.prev_action_evaluation: - emoji = '❌' - else: - emoji = '🤷' - - logger.info(f'{emoji} Eval: {response.current_state.prev_action_evaluation}') - logger.info(f'🧠 New Memory: {response.current_state.important_contents}') - logger.info(f'⏳ Task Progress: {response.current_state.completed_contents}') - logger.info(f'🤔 Thought: {response.current_state.thought}') - logger.info(f'🎯 Summary: {response.current_state.summary}') - for i, action in enumerate(response.action): - logger.info( - f'🛠️ Action {i + 1}/{len(response.action)}: {action.model_dump_json(exclude_unset=True)}' - ) - - def update_step_info(self, model_output: CustomAgentOutput, step_info: CustomAgentStepInfo = None): - """ - update step info - """ - if step_info is None: - return - - step_info.step_number += 1 - important_contents = model_output.current_state.important_contents - if important_contents and 'None' not in important_contents and important_contents not in step_info.memory: - step_info.memory += important_contents + '\n' - - completed_contents = model_output.current_state.completed_contents - if completed_contents and 'None' not in completed_contents: - step_info.task_progress = completed_contents - - @time_execution_async('--get_next_action') - async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutput: - """Get next action from LLM based on current state""" - - ret = self.llm.invoke(input_messages) - parsed_json = json.loads(ret.content.replace('```json', '').replace("```", "")) - parsed: AgentOutput = self.AgentOutput(**parsed_json) - # cut the number of actions to max_actions_per_step - parsed.action = parsed.action[: self.max_actions_per_step] - self._log_response(parsed) - self.n_steps += 1 - - return parsed - - @time_execution_async('--step') - async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None: - """Execute one step of the task""" - logger.info(f'\n📍 Step {self.n_steps}') - state = None - model_output = None - result: list[ActionResult] = [] - - try: - state = await self.browser_context.get_state(use_vision=self.use_vision) - self.message_manager.add_state_message(state, self._last_result, step_info) - input_messages = self.message_manager.get_messages() - model_output = await self.get_next_action(input_messages) - self.update_step_info(model_output, step_info) - logger.info(f'🧠 All Memory: {step_info.memory}') - self._save_conversation(input_messages, model_output) - self.message_manager._remove_last_state_message() # we dont want the whole state in the chat history - self.message_manager.add_model_output(model_output) - - result: list[ActionResult] = await self.controller.multi_act( - model_output.action, self.browser_context - ) - self._last_result = result - - if len(result) > 0 and result[-1].is_done: - logger.info(f'📄 Result: {result[-1].extracted_content}') - - self.consecutive_failures = 0 - - except Exception as e: - result = self._handle_step_error(e) - self._last_result = result - - finally: - if not result: - return - for r in result: - if r.error: - self.telemetry.capture( - AgentStepErrorTelemetryEvent( - agent_id=self.agent_id, - error=r.error, - ) - ) - if state: - self._make_history_item(model_output, state, result) - - async def run(self, max_steps: int = 100) -> AgentHistoryList: - """Execute the task with maximum number of steps""" - try: - logger.info(f'🚀 Starting task: {self.task}') - - self.telemetry.capture( - AgentRunTelemetryEvent( - agent_id=self.agent_id, - task=self.task, - ) - ) - - step_info = CustomAgentStepInfo(task=self.task, - add_infos=self.add_infos, - step_number=1, - max_steps=max_steps, - memory='', - task_progress='' - ) - - for step in range(max_steps): - if self._too_many_failures(): - break - - await self.step(step_info) - - if self.history.is_done(): - if ( - self.validate_output and step < max_steps - 1 - ): # if last step, we dont need to validate - if not await self._validate_output(): - continue - - logger.info('✅ Task completed successfully') - break - else: - logger.info('❌ Failed to complete task in maximum steps') - - return self.history - - finally: - self.telemetry.capture( - AgentEndTelemetryEvent( - agent_id=self.agent_id, - task=self.task, - success=self.history.is_done(), - steps=len(self.history.history), - ) - ) - if not self.injected_browser_context: - await self.browser_context.close() - - if not self.injected_browser and self.browser: - await self.browser.close() diff --git a/src/agent/custom_massage_manager.py b/src/agent/custom_massage_manager.py deleted file mode 100644 index 4af7d00c..00000000 --- a/src/agent/custom_massage_manager.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: custom_massage_manager.py - -from __future__ import annotations - -import logging -from datetime import datetime -from typing import List, Optional, Type - -from langchain_anthropic import ChatAnthropic -from langchain_core.language_models import BaseChatModel -from langchain_core.messages import ( - AIMessage, - BaseMessage, - HumanMessage, -) -from langchain_openai import ChatOpenAI - -from browser_use.agent.message_manager.views import MessageHistory, MessageMetadata -from browser_use.agent.prompts import AgentMessagePrompt, SystemPrompt -from browser_use.agent.views import ActionResult, AgentOutput, AgentStepInfo -from browser_use.browser.views import BrowserState -from browser_use.agent.message_manager.service import MessageManager - -from .custom_prompts import CustomAgentMessagePrompt - -logger = logging.getLogger(__name__) - - -class CustomMassageManager(MessageManager): - def __init__( - self, - llm: BaseChatModel, - task: str, - action_descriptions: str, - system_prompt_class: Type[SystemPrompt], - max_input_tokens: int = 128000, - estimated_tokens_per_character: int = 3, - image_tokens: int = 800, - include_attributes: list[str] = [], - max_error_length: int = 400, - max_actions_per_step: int = 10, - ): - super().__init__(llm, task, action_descriptions, system_prompt_class, max_input_tokens, - estimated_tokens_per_character, image_tokens, include_attributes, max_error_length, - max_actions_per_step) - - # Move Task info to state_message - self.history = MessageHistory() - self._add_message_with_tokens(self.system_prompt) - - def add_state_message( - self, - state: BrowserState, - result: Optional[List[ActionResult]] = None, - step_info: Optional[AgentStepInfo] = None, - ) -> None: - """Add browser state as human message""" - - # if keep in memory, add to directly to history and add state without result - if result: - for r in result: - if r.include_in_memory: - if r.extracted_content: - msg = HumanMessage(content=str(r.extracted_content)) - self._add_message_with_tokens(msg) - if r.error: - msg = HumanMessage(content=str(r.error)[-self.max_error_length:]) - self._add_message_with_tokens(msg) - result = None # if result in history, we dont want to add it again - - # otherwise add state message and result to next message (which will not stay in memory) - state_message = CustomAgentMessagePrompt( - state, - result, - include_attributes=self.include_attributes, - max_error_length=self.max_error_length, - step_info=step_info, - ).get_user_message() - self._add_message_with_tokens(state_message) diff --git a/src/agent/custom_prompts.py b/src/agent/custom_prompts.py deleted file mode 100644 index 0d88e413..00000000 --- a/src/agent/custom_prompts.py +++ /dev/null @@ -1,205 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: custom_prompts.py - -from datetime import datetime -from typing import List, Optional - -from langchain_core.messages import HumanMessage, SystemMessage - -from browser_use.agent.views import ActionResult, AgentStepInfo -from browser_use.browser.views import BrowserState -from browser_use.agent.prompts import SystemPrompt, AgentMessagePrompt - -from .custom_views import CustomAgentStepInfo - - -class CustomSystemPrompt(SystemPrompt): - def important_rules(self) -> str: - """ - Returns the important rules for the agent. - """ - text = """ - 1. RESPONSE FORMAT: You must ALWAYS respond with valid JSON in this exact format: - { - "current_state": { - "prev_action_evaluation": "Success|Failed|Unknown - Analyze the current elements and the image to check if the previous goals/actions are successful like intended by the task. Ignore the action result. The website is the ground truth. Also mention if something unexpected happened like new suggestions in an input field. Shortly state why/why not. Note that the result you output must be consistent with the reasoning you output afterwards. If you consider it to be 'Failed,' you should reflect on this during your thought.", - "important_contents": "Output important contents closely related to user\'s instruction or task on the current page. If there is, please output the contents. If not, please output \"None\".", - "completed_contents": "Update the input Task Progress. Completed contents is a general summary of the current contents that have been completed. Just summarize the contents that have been actually completed based on the current page and the history operations. Please list each completed item individually, such as: 1. Input username. 2. Input Password. 3. Click confirm button", - "thought": "Think about the requirements that have been completed in previous operations and the requirements that need to be completed in the next one operation. If the output of prev_action_evaluation is 'Failed', please reflect and output your reflection here. If you think you have entered the wrong page, consider to go back to the previous page in next action.", - "summary": "Please generate a brief natural language description for the operation in next actions based on your Thought." - }, - "action": [ - { - "action_name": { - // action-specific parameters - } - }, - // ... more actions in sequence - ] - } - - 2. ACTIONS: You can specify multiple actions to be executed in sequence. - - Common action sequences: - - Form filling: [ - {"input_text": {"index": 1, "text": "username"}}, - {"input_text": {"index": 2, "text": "password"}}, - {"click_element": {"index": 3}} - ] - - Navigation and extraction: [ - {"open_new_tab": {}}, - {"go_to_url": {"url": "https://example.com"}}, - {"extract_page_content": {}} - ] - - - 3. ELEMENT INTERACTION: - - Only use indexes that exist in the provided element list - - Each element has a unique index number (e.g., "33[:] - _[:] Non-interactive text - - - Notes: - - Only elements with numeric indexes are interactive - - _[:] elements provide context but cannot be interacted with - """ - - def get_system_message(self) -> SystemMessage: - """ - Get the system prompt for the agent. - - Returns: - str: Formatted system prompt - """ - time_str = self.current_date.strftime('%Y-%m-%d %H:%M') - - AGENT_PROMPT = f"""You are a precise browser automation agent that interacts with websites through structured commands. Your role is to: - 1. Analyze the provided webpage elements and structure - 2. Plan a sequence of actions to accomplish the given task - 3. Respond with valid JSON containing your action sequence and state assessment - - Current date and time: {time_str} - - {self.input_format()} - - {self.important_rules()} - - Functions: - {self.default_action_description} - - Remember: Your responses must be valid JSON matching the specified format. Each action in the sequence must be valid.""" - return SystemMessage(content=AGENT_PROMPT) - - -class CustomAgentMessagePrompt: - def __init__( - self, - state: BrowserState, - result: Optional[List[ActionResult]] = None, - include_attributes: list[str] = [], - max_error_length: int = 400, - step_info: Optional[CustomAgentStepInfo] = None, - ): - self.state = state - self.result = result - self.max_error_length = max_error_length - self.include_attributes = include_attributes - self.step_info = step_info - - def get_user_message(self) -> HumanMessage: - state_description = f""" - 1. Task: {self.step_info.task} - 2. Hints(Optional): - {self.step_info.add_infos} - 3. Memory: - {self.step_info.memory} - 4. Task Progress: - {self.step_info.task_progress} - 5. Current url: {self.state.url} - 6. Available tabs: - {self.state.tabs} - 7. Interactive elements: - {self.state.element_tree.clickable_elements_to_string(include_attributes=self.include_attributes)} - """ - - if self.result: - for i, result in enumerate(self.result): - if result.extracted_content: - state_description += ( - f'\nResult of action {i + 1}/{len(self.result)}: {result.extracted_content}' - ) - if result.error: - # only use last 300 characters of error - error = result.error[-self.max_error_length:] - state_description += f'\nError of action {i + 1}/{len(self.result)}: ...{error}' - - if self.state.screenshot: - # Format message for vision model - return HumanMessage( - content=[ - {'type': 'text', 'text': state_description}, - { - 'type': 'image_url', - 'image_url': {'url': f'data:image/png;base64,{self.state.screenshot}'}, - }, - ] - ) - - return HumanMessage(content=state_description) diff --git a/src/agent/custom_views.py b/src/agent/custom_views.py deleted file mode 100644 index d3e1647b..00000000 --- a/src/agent/custom_views.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: custom_views.py - -from dataclasses import dataclass -from typing import Type -from pydantic import BaseModel, ConfigDict, Field, ValidationError, create_model -from browser_use.controller.registry.views import ActionModel -from browser_use.agent.views import AgentOutput - - -@dataclass -class CustomAgentStepInfo: - step_number: int - max_steps: int - task: str - add_infos: str - memory: str - task_progress: str - - -class CustomAgentBrain(BaseModel): - """Current state of the agent""" - - prev_action_evaluation: str - important_contents: str - completed_contents: str - thought: str - summary: str - - -class CustomAgentOutput(AgentOutput): - """Output model for agent - - @dev note: this model is extended with custom actions in AgentService. You can also use some fields that are not in this model as provided by the linter, as long as they are registered in the DynamicActions model. - """ - - model_config = ConfigDict(arbitrary_types_allowed=True) - - current_state: CustomAgentBrain - action: list[ActionModel] - - @staticmethod - def type_with_custom_actions(custom_actions: Type[ActionModel]) -> Type['CustomAgentOutput']: - """Extend actions with custom actions""" - return create_model( - 'AgentOutput', - __base__=CustomAgentOutput, - action=(list[custom_actions], Field(...)), # Properly annotated field with no default - __module__=CustomAgentOutput.__module__, - ) diff --git a/src/browser/__init__.py b/src/browser/__init__.py deleted file mode 100644 index 93fbe7f8..00000000 --- a/src/browser/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: __init__.py.py diff --git a/src/browser/custom_browser.py b/src/browser/custom_browser.py deleted file mode 100644 index e6c6b16c..00000000 --- a/src/browser/custom_browser.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: browser.py - -from browser_use.browser.browser import Browser, BrowserConfig -from browser_use.browser.context import BrowserContextConfig, BrowserContext - -from .custom_context import CustomBrowserContext - - -class CustomBrowser(Browser): - - async def new_context( - self, config: BrowserContextConfig = BrowserContextConfig(), context: CustomBrowserContext = None - ) -> BrowserContext: - """Create a browser context""" - return CustomBrowserContext(config=config, browser=self, context=context) diff --git a/src/browser/custom_context.py b/src/browser/custom_context.py deleted file mode 100644 index 73356196..00000000 --- a/src/browser/custom_context.py +++ /dev/null @@ -1,96 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: context.py - -import asyncio -import base64 -import json -import logging -import os - -from playwright.async_api import Browser as PlaywrightBrowser -from browser_use.browser.context import BrowserContext, BrowserContextConfig -from browser_use.browser.browser import Browser - -logger = logging.getLogger(__name__) - - -class CustomBrowserContext(BrowserContext): - - def __init__( - self, - browser: 'Browser', - config: BrowserContextConfig = BrowserContextConfig(), - context: BrowserContext = None - ): - super(CustomBrowserContext, self).__init__(browser, config) - self.context = context - - async def _create_context(self, browser: PlaywrightBrowser): - """Creates a new browser context with anti-detection measures and loads cookies if available.""" - if self.context: - return self.context - if self.browser.config.chrome_instance_path and len(browser.contexts) > 0: - # Connect to existing Chrome instance instead of creating new one - context = browser.contexts[0] - else: - # Original code for creating new context - context = await browser.new_context( - viewport=self.config.browser_window_size, - no_viewport=False, - user_agent=( - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' - '(KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36' - ), - java_script_enabled=True, - bypass_csp=self.config.disable_security, - ignore_https_errors=self.config.disable_security, - record_video_dir=self.config.save_recording_path, - record_video_size=self.config.browser_window_size # set record video size - ) - - if self.config.trace_path: - await context.tracing.start(screenshots=True, snapshots=True, sources=True) - - # Load cookies if they exist - if self.config.cookies_file and os.path.exists(self.config.cookies_file): - with open(self.config.cookies_file, 'r') as f: - cookies = json.load(f) - logger.info(f'Loaded {len(cookies)} cookies from {self.config.cookies_file}') - await context.add_cookies(cookies) - - # Expose anti-detection scripts - await context.add_init_script( - """ - // Webdriver property - Object.defineProperty(navigator, 'webdriver', { - get: () => undefined - }); - - // Languages - Object.defineProperty(navigator, 'languages', { - get: () => ['en-US', 'en'] - }); - - // Plugins - Object.defineProperty(navigator, 'plugins', { - get: () => [1, 2, 3, 4, 5] - }); - - // Chrome runtime - window.chrome = { runtime: {} }; - - // Permissions - const originalQuery = window.navigator.permissions.query; - window.navigator.permissions.query = (parameters) => ( - parameters.name === 'notifications' ? - Promise.resolve({ state: Notification.permission }) : - originalQuery(parameters) - ); - """ - ) - - return context diff --git a/src/controller/__init__.py b/src/controller/__init__.py deleted file mode 100644 index b2eb1b38..00000000 --- a/src/controller/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: __init__.py.py diff --git a/src/controller/custom_controller.py b/src/controller/custom_controller.py deleted file mode 100644 index bd1c09e5..00000000 --- a/src/controller/custom_controller.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: custom_action.py - -import pyperclip - -from browser_use.controller.service import Controller -from browser_use.agent.views import ActionResult -from browser_use.browser.context import BrowserContext - - -class CustomController(Controller): - def __init__(self): - super().__init__() - self._register_custom_actions() - - def _register_custom_actions(self): - """Register all custom browser actions""" - - @self.registry.action('Copy text to clipboard') - def copy_to_clipboard(text: str): - pyperclip.copy(text) - return ActionResult(extracted_content=text) - - @self.registry.action('Paste text from clipboard', requires_browser=True) - async def paste_from_clipboard(browser: BrowserContext): - text = pyperclip.paste() - # send text to browser - page = await browser.get_current_page() - await page.keyboard.type(text) - - return ActionResult(extracted_content=text) diff --git a/src/utils/__init__.py b/src/utils/__init__.py deleted file mode 100644 index 93fbe7f8..00000000 --- a/src/utils/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: __init__.py.py diff --git a/src/utils/utils.py b/src/utils/utils.py deleted file mode 100644 index 6fbbd6c5..00000000 --- a/src/utils/utils.py +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: utils.py - -import base64 -import os - -from langchain_openai import ChatOpenAI, AzureChatOpenAI -from langchain_anthropic import ChatAnthropic -from langchain_google_genai import ChatGoogleGenerativeAI -from langchain_ollama import ChatOllama - - -def get_llm_model(provider: str, **kwargs): - """ - 获取LLM 模型 - :param provider: 模型类型 - :param kwargs: - :return: - """ - if provider == 'anthropic': - if not kwargs.get("base_url", ""): - base_url = "https://api.anthropic.com" - else: - base_url = kwargs.get("base_url") - - if not kwargs.get("api_key", ""): - api_key = os.getenv("ANTHROPIC_API_KEY", "") - else: - api_key = kwargs.get("api_key") - - return ChatAnthropic( - model_name=kwargs.get("model_name", 'claude-3-5-sonnet-20240620'), - temperature=kwargs.get("temperature", 0.0), - base_url=base_url, - api_key=api_key - ) - elif provider == 'openai': - if not kwargs.get("base_url", ""): - base_url = os.getenv("OPENAI_ENDPOINT", "https://api.openai.com/v1") - else: - base_url = kwargs.get("base_url") - - if not kwargs.get("api_key", ""): - api_key = os.getenv("OPENAI_API_KEY", "") - else: - api_key = kwargs.get("api_key") - - return ChatOpenAI( - model=kwargs.get("model_name", 'gpt-4o'), - temperature=kwargs.get("temperature", 0.0), - base_url=base_url, - api_key=api_key - ) - elif provider == 'deepseek': - if not kwargs.get("base_url", ""): - base_url = os.getenv("DEEPSEEK_ENDPOINT", "") - else: - base_url = kwargs.get("base_url") - - if not kwargs.get("api_key", ""): - api_key = os.getenv("DEEPSEEK_API_KEY", "") - else: - api_key = kwargs.get("api_key") - - return ChatOpenAI( - model=kwargs.get("model_name", 'deepseek-chat'), - temperature=kwargs.get("temperature", 0.0), - base_url=base_url, - api_key=api_key - ) - elif provider == 'gemini': - if not kwargs.get("api_key", ""): - api_key = os.getenv("GOOGLE_API_KEY", "") - else: - api_key = kwargs.get("api_key") - return ChatGoogleGenerativeAI( - model=kwargs.get("model_name", 'gemini-2.0-flash-exp'), - temperature=kwargs.get("temperature", 0.0), - google_api_key=api_key, - ) - elif provider == 'ollama': - return ChatOllama( - model=kwargs.get("model_name", 'qwen2.5:7b'), - temperature=kwargs.get("temperature", 0.0), - ) - elif provider == "azure_openai": - if not kwargs.get("base_url", ""): - base_url = os.getenv("AZURE_OPENAI_ENDPOINT", "") - else: - base_url = kwargs.get("base_url") - if not kwargs.get("api_key", ""): - api_key = os.getenv("AZURE_OPENAI_API_KEY", "") - else: - api_key = kwargs.get("api_key") - return AzureChatOpenAI( - model=kwargs.get("model_name", 'gpt-4o'), - temperature=kwargs.get("temperature", 0.0), - api_version="2024-05-01-preview", - azure_endpoint=base_url, - api_key=api_key - ) - else: - raise ValueError(f'Unsupported provider: {provider}') - - -def encode_image(img_path): - if not img_path: - return None - with open(img_path, "rb") as fin: - image_data = base64.b64encode(fin.read()).decode("utf-8") - return image_data diff --git a/src/web_ui/__init__.py b/src/web_ui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/web_ui/agent/__init__.py b/src/web_ui/agent/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/web_ui/agent/browser_use/browser_use_agent.py b/src/web_ui/agent/browser_use/browser_use_agent.py new file mode 100644 index 00000000..67bd4a23 --- /dev/null +++ b/src/web_ui/agent/browser_use/browser_use_agent.py @@ -0,0 +1,216 @@ +from __future__ import annotations + +import asyncio +import logging +import os + +# from lmnr.sdk.decorators import observe +from browser_use.agent.gif import create_history_gif +from browser_use.agent.message_manager.utils import is_model_without_tool_support +from browser_use.agent.service import Agent, AgentHookFunc +from browser_use.agent.views import ( + ActionResult, + AgentHistory, + AgentHistoryList, + AgentStepInfo, + ToolCallingMethod, +) +from browser_use.browser.views import BrowserStateHistory +from browser_use.utils import time_execution_async +from dotenv import load_dotenv + +load_dotenv() +logger = logging.getLogger(__name__) + +SKIP_LLM_API_KEY_VERIFICATION = ( + os.environ.get("SKIP_LLM_API_KEY_VERIFICATION", "false").lower()[0] in "ty1" +) + + +class BrowserUseAgent(Agent): + def _set_tool_calling_method(self) -> ToolCallingMethod | None: + tool_calling_method = self.settings.tool_calling_method + if tool_calling_method == "auto": + if is_model_without_tool_support(self.model_name): + return "raw" + elif self.chat_model_library == "ChatGoogleGenerativeAI": + return None + elif self.chat_model_library == "ChatOpenAI": + return "function_calling" + elif self.chat_model_library == "AzureChatOpenAI": + return "function_calling" + else: + return None + else: + return tool_calling_method + + def get_mcp_tools_info(self) -> dict[str, list[str]]: + """ + Get information about available MCP tools from the controller. + + Returns: + Dictionary mapping MCP server names to lists of tool names + """ + # Import here to avoid circular dependency + from src.web_ui.controller.custom_controller import CustomController + + if isinstance(self.controller, CustomController): + return self.controller.get_registered_mcp_tools() + return {} + + def list_available_mcp_tools(self) -> str: + """ + Get a formatted string listing all available MCP tools. + + Returns: + Human-readable string describing available MCP tools + """ + mcp_tools = self.get_mcp_tools_info() + + if not mcp_tools: + return "No MCP tools are currently available." + + lines = [f"Available MCP Tools ({sum(len(tools) for tools in mcp_tools.values())} total):"] + for server_name, tools in mcp_tools.items(): + lines.append(f"\n 📦 {server_name} ({len(tools)} tools):") + for tool_name in tools: + lines.append(f" - {tool_name}") + + return "\n".join(lines) + + @time_execution_async("--run (agent)") + async def run( + self, + max_steps: int = 100, + on_step_start: AgentHookFunc | None = None, + on_step_end: AgentHookFunc | None = None, + ) -> AgentHistoryList: + """Execute the task with maximum number of steps""" + + loop = asyncio.get_event_loop() + + # Set up the Ctrl+C signal handler with callbacks specific to this agent + from browser_use.utils import SignalHandler + + signal_handler = SignalHandler( + loop=loop, + pause_callback=self.pause, + resume_callback=self.resume, + custom_exit_callback=None, # No special cleanup needed on forced exit + exit_on_second_int=True, + ) + signal_handler.register() + + try: + self._log_agent_run() + + # Log available MCP tools + mcp_tools_info = self.list_available_mcp_tools() + if "No MCP tools" not in mcp_tools_info: + logger.info(f"\n{mcp_tools_info}") + + # Execute initial actions if provided + if self.initial_actions: + result = await self.multi_act(self.initial_actions, check_for_new_elements=False) + self.state.last_result = result + + for step in range(max_steps): + # Check if waiting for user input after Ctrl+C + if self.state.paused: + signal_handler.wait_for_resume() + signal_handler.reset() + + # Check if we should stop due to too many failures + if self.state.consecutive_failures >= self.settings.max_failures: + logger.error( + f"❌ Stopping due to {self.settings.max_failures} consecutive failures" + ) + break + + # Check control flags before each step + if self.state.stopped: + logger.info("Agent stopped") + break + + while self.state.paused: + await asyncio.sleep(0.2) # Small delay to prevent CPU spinning + if self.state.stopped: # Allow stopping while paused + break + + if on_step_start is not None: + await on_step_start(self) + + step_info = AgentStepInfo(step_number=step, max_steps=max_steps) + await self.step(step_info) + + if on_step_end is not None: + await on_step_end(self) + + if self.state.history.is_done(): + if self.settings.validate_output and step < max_steps - 1: + if not await self._validate_output(): + continue + + await self.log_completion() + break + else: + error_message = "Failed to complete task in maximum steps" + + self.state.history.history.append( + AgentHistory( + model_output=None, + result=[ActionResult(error=error_message, include_in_memory=True)], + state=BrowserStateHistory( + url="", + title="", + tabs=[], + interacted_element=[], + screenshot=None, + ), + metadata=None, + ) + ) + + logger.info(f"❌ {error_message}") + + return self.state.history + + except KeyboardInterrupt: + # Already handled by our signal handler, but catch any direct KeyboardInterrupt as well + logger.info("Got KeyboardInterrupt during execution, returning current history") + return self.state.history + + finally: + # Unregister signal handlers before cleanup + signal_handler.unregister() + + if self.settings.save_playwright_script_path: + logger.info( + f"Agent run finished. Attempting to save Playwright script to: {self.settings.save_playwright_script_path}" + ) + try: + # Extract sensitive data keys if sensitive_data is provided + keys = list(self.sensitive_data.keys()) if self.sensitive_data else None + # Pass browser and context config to the saving method + self.state.history.save_as_playwright_script( + self.settings.save_playwright_script_path, + sensitive_data_keys=keys, + browser_config=self.browser.config, + context_config=self.browser_context.config, + ) + except Exception as script_gen_err: + # Log any error during script generation/saving + logger.error( + f"Failed to save Playwright script: {script_gen_err}", exc_info=True + ) + + await self.close() + + if self.settings.generate_gif: + output_path: str = "agent_history.gif" + if isinstance(self.settings.generate_gif, str): + output_path = self.settings.generate_gif + + create_history_gif( + task=self.task, history=self.state.history, output_path=output_path + ) diff --git a/src/web_ui/agent/deep_research/deep_research_agent.py b/src/web_ui/agent/deep_research/deep_research_agent.py new file mode 100644 index 00000000..bd87f006 --- /dev/null +++ b/src/web_ui/agent/deep_research/deep_research_agent.py @@ -0,0 +1,1383 @@ +import asyncio +import json +import logging +import os +import threading +import uuid +from pathlib import Path +from typing import Any, TypedDict + +from browser_use.browser.browser import BrowserConfig +from browser_use.browser.context import BrowserContextConfig +from langchain_community.tools.file_management import ( + ListDirectoryTool, + ReadFileTool, + WriteFileTool, +) + +# Langchain imports +from langchain_core.messages import ( + AIMessage, + BaseMessage, + HumanMessage, + SystemMessage, + ToolMessage, +) +from langchain_core.prompts import ChatPromptTemplate +from langchain_core.tools import StructuredTool, Tool + +# Langgraph imports +from langgraph.graph import StateGraph +from pydantic import BaseModel, Field + +from src.web_ui.agent.browser_use.browser_use_agent import BrowserUseAgent +from src.web_ui.browser.custom_browser import CustomBrowser +from src.web_ui.controller.custom_controller import CustomController +from src.web_ui.utils.mcp_client import setup_mcp_client_and_tools + +logger = logging.getLogger(__name__) + +# Constants +REPORT_FILENAME = "report.md" +PLAN_FILENAME = "research_plan.md" +SEARCH_INFO_FILENAME = "search_info.json" + +_AGENT_STOP_FLAGS = {} +_BROWSER_AGENT_INSTANCES = {} + + +async def run_single_browser_task( + task_query: str, + task_id: str, + llm: Any, # Pass the main LLM + browser_config: dict[str, Any], + stop_event: threading.Event, + use_vision: bool = False, +) -> dict[str, Any]: + """ + Runs a single BrowserUseAgent task. + Manages browser creation and closing for this specific task. + """ + if not BrowserUseAgent: + return { + "query": task_query, + "error": "BrowserUseAgent components not available.", + } + + # --- Browser Setup --- + # These should ideally come from the main agent's config + headless = browser_config.get("headless", False) + window_w = browser_config.get("window_width", 1280) + window_h = browser_config.get("window_height", 1100) + browser_user_data_dir = browser_config.get("user_data_dir", None) + use_own_browser = browser_config.get("use_own_browser", False) + browser_binary_path = browser_config.get("browser_binary_path", None) + wss_url = browser_config.get("wss_url", None) + cdp_url = browser_config.get("cdp_url", None) + + bu_browser = None + bu_browser_context = None + try: + logger.info(f"Starting browser task for query: {task_query}") + extra_args = [] + if use_own_browser: + browser_binary_path = os.getenv("BROWSER_PATH", None) or browser_binary_path + if browser_binary_path == "": + browser_binary_path = None + browser_user_data = browser_user_data_dir or os.getenv("BROWSER_USER_DATA", None) + if browser_user_data: + extra_args += [f"--user-data-dir={browser_user_data}"] + else: + browser_binary_path = None + + bu_browser = CustomBrowser( + config=BrowserConfig( + headless=headless, + browser_binary_path=browser_binary_path, + extra_browser_args=extra_args, + wss_url=wss_url, + cdp_url=cdp_url, + new_context_config=BrowserContextConfig( + window_width=window_w, + window_height=window_h, + ), + ) + ) + + context_config = BrowserContextConfig( + save_downloads_path="./tmp/downloads", + window_height=window_h, + window_width=window_w, + force_new_context=True, + ) + bu_browser_context = await bu_browser.new_context(config=context_config) + + # Simple controller example, replace with your actual implementation if needed + bu_controller = CustomController() + + # Construct the task prompt for BrowserUseAgent + # Instruct it to find specific info and return title/URL + bu_task_prompt = f""" + Research Task: {task_query} + Objective: Find relevant information answering the query. + Output Requirements: For each relevant piece of information found, please provide: + 1. A concise summary of the information. + 2. The title of the source page or document. + 3. The URL of the source. + Focus on accuracy and relevance. Avoid irrelevant details. + PDF cannot directly extract _content, please try to download first, then using read_file, if you can't save or read, please try other methods. + + Available Tools: You have access to browser automation tools and MCP (Model Context Protocol) tools. + MCP tools provide additional capabilities like file system access, web search, database operations, and more. + Use MCP tools when they can help accomplish the research task more effectively than browser automation alone. + """ + + bu_agent_instance = BrowserUseAgent( + task=bu_task_prompt, + llm=llm, # Use the passed LLM + browser=bu_browser, + browser_context=bu_browser_context, + controller=bu_controller, + use_vision=use_vision, + source="webui", + ) + + # Store instance for potential stop() call + task_key = f"{task_id}_{uuid.uuid4()}" + _BROWSER_AGENT_INSTANCES[task_key] = bu_agent_instance + + # --- Run with Stop Check --- + # BrowserUseAgent needs to internally check a stop signal or have a stop method. + # We simulate checking before starting and assume `run` might be interruptible + # or have its own stop mechanism we can trigger via bu_agent_instance.stop(). + if stop_event.is_set(): + logger.info(f"Browser task for '{task_query}' cancelled before start.") + return {"query": task_query, "result": None, "status": "cancelled"} + + # The run needs to be awaitable and ideally accept a stop signal or have a .stop() method + # result = await bu_agent_instance.run(max_steps=max_steps) # Add max_steps if applicable + # Let's assume a simplified run for now + logger.info(f"Running BrowserUseAgent for: {task_query}") + result = await bu_agent_instance.run() # Assuming run is the main method + logger.info(f"BrowserUseAgent finished for: {task_query}") + + final_data = result.final_result() + + if stop_event.is_set(): + logger.info(f"Browser task for '{task_query}' stopped during execution.") + return {"query": task_query, "result": final_data, "status": "stopped"} + else: + logger.info(f"Browser result for '{task_query}': {final_data}") + return {"query": task_query, "result": final_data, "status": "completed"} + + except Exception as e: + logger.error(f"Error during browser task for query '{task_query}': {e}", exc_info=True) + return {"query": task_query, "error": str(e), "status": "failed"} + finally: + if bu_browser_context: + try: + await bu_browser_context.close() + bu_browser_context = None + logger.info("Closed browser context.") + except Exception as e: + logger.error(f"Error closing browser context: {e}") + if bu_browser: + try: + await bu_browser.close() + bu_browser = None + logger.info("Closed browser.") + except Exception as e: + logger.error(f"Error closing browser: {e}") + + if task_key in _BROWSER_AGENT_INSTANCES: + del _BROWSER_AGENT_INSTANCES[task_key] + + +class BrowserSearchInput(BaseModel): + queries: list[str] = Field( + description="List of distinct search queries to find information relevant to the research task." + ) + + +async def _run_browser_search_tool( + queries: list[str], + task_id: str, # Injected dependency + llm: Any, # Injected dependency + browser_config: dict[str, Any], + stop_event: threading.Event, + max_parallel_browsers: int = 1, +) -> list[dict[str, Any]]: + """ + Internal function to execute parallel browser searches based on LLM-provided queries. + Handles concurrency and stop signals. + """ + + # Limit queries just in case LLM ignores the description + queries = queries[:max_parallel_browsers] + logger.info(f"[Browser Tool {task_id}] Running search for {len(queries)} queries: {queries}") + + semaphore = asyncio.Semaphore(max_parallel_browsers) + + async def task_wrapper(query): + async with semaphore: + if stop_event.is_set(): + logger.info(f"[Browser Tool {task_id}] Skipping task due to stop signal: {query}") + return {"query": query, "result": None, "status": "cancelled"} + # Pass necessary injected configs and the stop event + return await run_single_browser_task( + query, + task_id, + llm, # Pass the main LLM (or a dedicated one if needed) + browser_config, + stop_event, + # use_vision could be added here if needed + ) + + tasks = [task_wrapper(query) for query in queries] + search_results = await asyncio.gather(*tasks, return_exceptions=True) + + processed_results = [] + for i, res in enumerate(search_results): + query = queries[i] # Get corresponding query + if isinstance(res, Exception): + logger.error( + f"[Browser Tool {task_id}] Gather caught exception for query '{query}': {res}", + exc_info=True, + ) + processed_results.append({"query": query, "error": str(res), "status": "failed"}) + elif isinstance(res, dict): + processed_results.append(res) + else: + logger.error( + f"[Browser Tool {task_id}] Unexpected result type for query '{query}': {type(res)}" + ) + processed_results.append( + {"query": query, "error": "Unexpected result type", "status": "failed"} + ) + + logger.info( + f"[Browser Tool {task_id}] Finished search. Results count: {len(processed_results)}" + ) + return processed_results + + +def create_browser_search_tool( + llm: Any, + browser_config: dict[str, Any], + task_id: str, + stop_event: threading.Event, + max_parallel_browsers: int = 1, +) -> StructuredTool: + """Factory function to create the browser search tool with necessary dependencies.""" + # Use partial to bind the dependencies that aren't part of the LLM call arguments + from functools import partial + + bound_tool_func = partial( + _run_browser_search_tool, + task_id=task_id, + llm=llm, + browser_config=browser_config, + stop_event=stop_event, + max_parallel_browsers=max_parallel_browsers, + ) + + return StructuredTool.from_function( + coroutine=bound_tool_func, + name="parallel_browser_search", + description=f"""Use this tool to actively search the web for information related to a specific research task or question. +It runs up to {max_parallel_browsers} searches in parallel using a browser agent for better results than simple scraping. +Provide a list of distinct search queries(up to {max_parallel_browsers}) that are likely to yield relevant information.""", + args_schema=BrowserSearchInput, + ) + + +# --- Langgraph State Definition --- + + +class ResearchTaskItem(TypedDict): + # step: int # Maybe step within category, or just implicit by order + task_description: str + status: str # "pending", "completed", "failed" + queries: list[str] | None + result_summary: str | None + + +class ResearchCategoryItem(TypedDict): + category_name: str + tasks: list[ResearchTaskItem] + # Optional: category_status: str # Could be "pending", "in_progress", "completed" + + +class DeepResearchState(TypedDict): + task_id: str + topic: str + research_plan: list[ResearchCategoryItem] # CHANGED + search_results: list[dict[str, Any]] + llm: Any + tools: list[Tool] + output_dir: Path + browser_config: dict[str, Any] + final_report: str | None + current_category_index: int + current_task_index_in_category: int + stop_requested: bool + error_message: str | None + messages: list[BaseMessage] + + +# --- Langgraph Nodes --- + + +def _load_previous_state(task_id: str, output_dir: str) -> dict[str, Any]: + state_updates = {} + plan_file = os.path.join(output_dir, PLAN_FILENAME) + search_file = os.path.join(output_dir, SEARCH_INFO_FILENAME) + + loaded_plan: list[ResearchCategoryItem] = [] + next_cat_idx, next_task_idx = 0, 0 + found_pending = False + + if os.path.exists(plan_file): + try: + with open(plan_file, encoding="utf-8") as f: + current_category: ResearchCategoryItem | None = None + lines = f.readlines() + cat_counter = 0 + task_counter_in_cat = 0 + + for _line_num, line_content in enumerate(lines): + line = line_content.strip() + if line.startswith("## "): # Category + if current_category: # Save previous category + loaded_plan.append(current_category) + if ( + not found_pending + ): # If previous category was all done, advance cat counter + cat_counter += 1 + task_counter_in_cat = 0 + category_name = line[line.find(" ") :].strip() # Get text after "## X. " + current_category = ResearchCategoryItem( + category_name=category_name, tasks=[] + ) + elif ( + line.startswith("- [ ]") + or line.startswith("- [x]") + or line.startswith("- [-]") + ) and current_category: # Task + status = "pending" + if line.startswith("- [x]"): + status = "completed" + elif line.startswith("- [-]"): + status = "failed" + + task_desc = line[5:].strip() + current_category["tasks"].append( + ResearchTaskItem( + task_description=task_desc, + status=status, + queries=None, + result_summary=None, + ) + ) + if status == "pending" and not found_pending: + next_cat_idx = cat_counter + next_task_idx = task_counter_in_cat + found_pending = True + if ( + not found_pending + ): # only increment if previous tasks were completed/failed + task_counter_in_cat += 1 + + if current_category: # Append last category + loaded_plan.append(current_category) + + if loaded_plan: + state_updates["research_plan"] = loaded_plan + if not found_pending and loaded_plan: # All tasks were completed or failed + next_cat_idx = len(loaded_plan) # Points beyond the last category + next_task_idx = 0 + state_updates["current_category_index"] = next_cat_idx + state_updates["current_task_index_in_category"] = next_task_idx + logger.info( + f"Loaded hierarchical research plan from {plan_file}. " + f"Next task: Category {next_cat_idx}, Task {next_task_idx} in category." + ) + else: + logger.warning(f"Plan file {plan_file} was empty or malformed.") + + except Exception as e: + logger.error(f"Failed to load or parse research plan {plan_file}: {e}", exc_info=True) + state_updates["error_message"] = f"Failed to load research plan: {e}" + else: + logger.info(f"Plan file {plan_file} not found. Will start fresh.") + + if os.path.exists(search_file): + try: + with open(search_file, encoding="utf-8") as f: + state_updates["search_results"] = json.load(f) + logger.info(f"Loaded search results from {search_file}") + except Exception as e: + logger.error(f"Failed to load search results {search_file}: {e}") + state_updates["error_message"] = ( + state_updates.get("error_message", "") + f" Failed to load search results: {e}" + ).strip() + + return state_updates + + +def _save_plan_to_md(plan: list[ResearchCategoryItem], output_dir: str): + plan_file = os.path.join(output_dir, PLAN_FILENAME) + try: + with open(plan_file, "w", encoding="utf-8") as f: + f.write("# Research Plan\n\n") + for cat_idx, category in enumerate(plan): + f.write(f"## {cat_idx + 1}. {category['category_name']}\n\n") + for _task_idx, task in enumerate(category["tasks"]): + marker = ( + "- [x]" + if task["status"] == "completed" + else "- [ ]" + if task["status"] == "pending" + else "- [-]" + ) # [-] for failed + f.write(f" {marker} {task['task_description']}\n") + f.write("\n") + logger.info(f"Hierarchical research plan saved to {plan_file}") + except Exception as e: + logger.error(f"Failed to save research plan to {plan_file}: {e}") + + +def _save_search_results_to_json(results: list[dict[str, Any]], output_dir: str): + """Appends or overwrites search results to a JSON file.""" + search_file = os.path.join(output_dir, SEARCH_INFO_FILENAME) + try: + # Simple overwrite for now, could be append + with open(search_file, "w", encoding="utf-8") as f: + json.dump(results, f, indent=2, ensure_ascii=False) + logger.info(f"Search results saved to {search_file}") + except Exception as e: + logger.error(f"Failed to save search results to {search_file}: {e}") + + +def _save_report_to_md(report: str, output_dir: Path): + """Saves the final report to a markdown file.""" + report_file = os.path.join(output_dir, REPORT_FILENAME) + try: + with open(report_file, "w", encoding="utf-8") as f: + f.write(report) + logger.info(f"Final report saved to {report_file}") + except Exception as e: + logger.error(f"Failed to save final report to {report_file}: {e}") + + +async def planning_node(state: DeepResearchState) -> dict[str, Any]: + logger.info("--- Entering Planning Node ---") + if state.get("stop_requested"): + logger.info("Stop requested, skipping planning.") + return {"stop_requested": True} + + llm = state["llm"] + topic = state["topic"] + existing_plan = state.get("research_plan") + output_dir = state["output_dir"] + + if existing_plan and ( + state.get("current_category_index", 0) > 0 + or state.get("current_task_index_in_category", 0) > 0 + ): + logger.info("Resuming with existing plan.") + _save_plan_to_md(existing_plan, str(output_dir)) # Ensure it's saved initially + # current_category_index and current_task_index_in_category should be set by _load_previous_state + return {"research_plan": existing_plan} + + logger.info(f"Generating new research plan for topic: {topic}") + + prompt_text = f"""You are a meticulous research assistant. Your goal is to create a hierarchical research plan to thoroughly investigate the topic: "{topic}". +The plan should be structured into several main research categories. Each category should contain a list of specific, actionable research tasks or questions. +Format the output as a JSON list of objects. Each object represents a research category and should have: +1. "category_name": A string for the name of the research category. +2. "tasks": A list of strings, where each string is a specific research task for that category. + +Example JSON Output: +[ + {{ + "category_name": "Understanding Core Concepts and Definitions", + "tasks": [ + "Define the primary terminology associated with '{topic}'.", + "Identify the fundamental principles and theories underpinning '{topic}'." + ] + }}, + {{ + "category_name": "Historical Development and Key Milestones", + "tasks": [ + "Trace the historical evolution of '{topic}'.", + "Identify key figures, events, or breakthroughs in the development of '{topic}'." + ] + }}, + {{ + "category_name": "Current State-of-the-Art and Applications", + "tasks": [ + "Analyze the current advancements and prominent applications of '{topic}'.", + "Investigate ongoing research and active areas of development related to '{topic}'." + ] + }}, + {{ + "category_name": "Challenges, Limitations, and Future Outlook", + "tasks": [ + "Identify the major challenges and limitations currently facing '{topic}'.", + "Explore potential future trends, ethical considerations, and societal impacts of '{topic}'." + ] + }} +] + +Generate a plan with 3-10 categories, and 2-6 tasks per category for the topic: "{topic}" according to the complexity of the topic. +Ensure the output is a valid JSON array. +""" + messages = [ + SystemMessage(content="You are a research planning assistant outputting JSON."), + HumanMessage(content=prompt_text), + ] + + try: + response = await llm.ainvoke(messages) + raw_content = response.content + # The LLM might wrap the JSON in backticks + if raw_content.strip().startswith("```json"): + raw_content = raw_content.strip()[7:-3].strip() + elif raw_content.strip().startswith("```"): + raw_content = raw_content.strip()[3:-3].strip() + + logger.debug(f"LLM response for plan: {raw_content}") + parsed_plan_from_llm = json.loads(raw_content) + + new_plan: list[ResearchCategoryItem] = [] + for _cat_idx, category_data in enumerate(parsed_plan_from_llm): + if ( + not isinstance(category_data, dict) + or "category_name" not in category_data + or "tasks" not in category_data + ): + logger.warning(f"Skipping invalid category data: {category_data}") + continue + + tasks: list[ResearchTaskItem] = [] + for _task_idx, task_desc in enumerate(category_data["tasks"]): + if isinstance(task_desc, str): + tasks.append( + ResearchTaskItem( + task_description=task_desc, + status="pending", + queries=None, + result_summary=None, + ) + ) + else: # Sometimes LLM puts tasks as {"task": "description"} + if isinstance(task_desc, dict) and "task_description" in task_desc: + tasks.append( + ResearchTaskItem( + task_description=task_desc["task_description"], + status="pending", + queries=None, + result_summary=None, + ) + ) + elif isinstance(task_desc, dict) and "task" in task_desc: # common LLM mistake + tasks.append( + ResearchTaskItem( + task_description=task_desc["task"], + status="pending", + queries=None, + result_summary=None, + ) + ) + else: + logger.warning( + f"Skipping invalid task data: {task_desc} in category {category_data['category_name']}" + ) + + new_plan.append( + ResearchCategoryItem( + category_name=category_data["category_name"], + tasks=tasks, + ) + ) + + if not new_plan: + logger.error("LLM failed to generate a valid plan structure from JSON.") + return {"error_message": "Failed to generate research plan structure."} + + logger.info(f"Generated research plan with {len(new_plan)} categories.") + _save_plan_to_md(new_plan, str(output_dir)) # Save the hierarchical plan + + return { + "research_plan": new_plan, + "current_category_index": 0, + "current_task_index_in_category": 0, + "search_results": [], + } + + except json.JSONDecodeError as e: + logger.error( + f"Failed to parse JSON from LLM for plan: {e}. Response was: {raw_content}", + exc_info=True, + ) + return {"error_message": f"LLM generated invalid JSON for research plan: {e}"} + except Exception as e: + logger.error(f"Error during planning: {e}", exc_info=True) + return {"error_message": f"LLM Error during planning: {e}"} + + +async def research_execution_node(state: DeepResearchState) -> dict[str, Any]: + logger.info("--- Entering Research Execution Node ---") + if state.get("stop_requested"): + logger.info("Stop requested, skipping research execution.") + return { + "stop_requested": True, + "current_category_index": state["current_category_index"], + "current_task_index_in_category": state["current_task_index_in_category"], + } + + plan = state["research_plan"] + cat_idx = state["current_category_index"] + task_idx = state["current_task_index_in_category"] + llm = state["llm"] + tools = state["tools"] + output_dir = str(state["output_dir"]) + task_id = state["task_id"] # For _AGENT_STOP_FLAGS + + # This check should ideally be handled by `should_continue` + if not plan or cat_idx >= len(plan): + logger.info("Research plan complete or categories exhausted.") + return {} # should route to synthesis + + current_category = plan[cat_idx] + if task_idx >= len(current_category["tasks"]): + logger.info( + f"All tasks in category '{current_category['category_name']}' completed. Moving to next category." + ) + # This logic is now effectively handled by should_continue and the index updates below + # The next iteration will be caught by should_continue or this node with updated indices + return { + "current_category_index": cat_idx + 1, + "current_task_index_in_category": 0, + "messages": state["messages"], # Pass messages along + } + + current_task = current_category["tasks"][task_idx] + + if current_task["status"] == "completed": + logger.info( + f"Task '{current_task['task_description']}' in category '{current_category['category_name']}' already completed. Skipping." + ) + # Logic to find next task + next_task_idx = task_idx + 1 + next_cat_idx = cat_idx + if next_task_idx >= len(current_category["tasks"]): + next_cat_idx += 1 + next_task_idx = 0 + return { + "current_category_index": next_cat_idx, + "current_task_index_in_category": next_task_idx, + "messages": state["messages"], # Pass messages along + } + + logger.info( + f"Executing research task: '{current_task['task_description']}' (Category: '{current_category['category_name']}')" + ) + + llm_with_tools = llm.bind_tools(tools) + + # Construct messages for LLM invocation + task_prompt_content = ( + f"Current Research Category: {current_category['category_name']}\n" + f"Specific Task: {current_task['task_description']}\n\n" + "Please use the available tools to gather information for this specific task. " + "You have access to browser automation tools (parallel_browser_search) and MCP (Model Context Protocol) tools. " + "MCP tools provide additional capabilities like file system access, web search, database operations, memory storage, and more. " + "Use the most appropriate tool for each task - MCP tools for structured data access and browser tools for web exploration. " + "Provide focused search queries relevant ONLY to this task. " + "If you believe you have sufficient information from previous steps for this specific task, you can indicate that you are ready to summarize or that no further search is needed." + ) + current_task_message_history = [HumanMessage(content=task_prompt_content)] + if not state["messages"]: # First actual execution message + invocation_messages = [ + SystemMessage( + content="You are a research assistant executing one task of a research plan. Focus on the current task only. " + "You have access to browser automation tools and MCP (Model Context Protocol) tools. " + "Use MCP tools for structured data access, file operations, web search, database queries, and memory storage. " + "Use browser tools for web exploration and interaction. Choose the most appropriate tool for each task." + ), + ] + current_task_message_history + else: + invocation_messages = state["messages"] + current_task_message_history + + try: + logger.info(f"Invoking LLM with tools for task: {current_task['task_description']}") + ai_response: BaseMessage = await llm_with_tools.ainvoke(invocation_messages) + logger.info("LLM invocation complete.") + + tool_results = [] + executed_tool_names = [] + current_search_results = state.get("search_results", []) # Get existing search results + + if not isinstance(ai_response, AIMessage) or not ai_response.tool_calls: + logger.warning( + f"LLM did not call any tool for task '{current_task['task_description']}'. Response: {ai_response.content[:100]}..." + ) + current_task["status"] = "pending" # Or "completed_no_tool" if LLM explains it's done + current_task["result_summary"] = ( + f"LLM did not use a tool. Response: {ai_response.content}" + ) + current_task["current_category_index"] = cat_idx + current_task["current_task_index_in_category"] = task_idx + return current_task + # We still save the plan and advance. + else: + # Process tool calls + for tool_call in ai_response.tool_calls: + tool_name = tool_call.get("name") + tool_args = tool_call.get("args", {}) + tool_call_id = tool_call.get("id") + + logger.info(f"LLM requested tool call: {tool_name} with args: {tool_args}") + executed_tool_names.append(tool_name) + selected_tool = next((t for t in tools if t.name == tool_name), None) + + if not selected_tool: + logger.error(f"LLM called tool '{tool_name}' which is not available.") + tool_results.append( + ToolMessage( + content=f"Error: Tool '{tool_name}' not found.", + tool_call_id=tool_call_id, + ) + ) + continue + + try: + stop_event = _AGENT_STOP_FLAGS.get(task_id) + if stop_event and stop_event.is_set(): + logger.info(f"Stop requested before executing tool: {tool_name}") + current_task["status"] = "pending" # Or a new "stopped" status + _save_plan_to_md(plan, output_dir) + return { + "stop_requested": True, + "research_plan": plan, + "current_category_index": cat_idx, + "current_task_index_in_category": task_idx, + } + + logger.info(f"Executing tool: {tool_name}") + tool_output = await selected_tool.ainvoke(tool_args) + logger.info(f"Tool '{tool_name}' executed successfully.") + + if tool_name == "parallel_browser_search": + current_search_results.extend(tool_output) # tool_output is List[Dict] + else: # For other tools, we might need specific handling or just log + logger.info(f"Result from tool '{tool_name}': {str(tool_output)[:200]}...") + # Storing non-browser results might need a different structure or key in search_results + current_search_results.append( + { + "tool_name": tool_name, + "args": tool_args, + "output": str(tool_output), + "status": "completed", + } + ) + + tool_results.append( + ToolMessage(content=json.dumps(tool_output), tool_call_id=tool_call_id) + ) + + except Exception as e: + logger.error(f"Error executing tool '{tool_name}': {e}", exc_info=True) + tool_results.append( + ToolMessage( + content=f"Error executing tool {tool_name}: {e}", + tool_call_id=tool_call_id, + ) + ) + current_search_results.append( + { + "tool_name": tool_name, + "args": tool_args, + "status": "failed", + "error": str(e), + } + ) + + # After processing all tool calls for this task + step_failed_tool_execution = any("Error:" in str(tr.content) for tr in tool_results) + # Consider a task successful if a browser search was attempted and didn't immediately error out during call + # The browser search itself returns status for each query. + + if step_failed_tool_execution: + current_task["status"] = "failed" + current_task["result_summary"] = ( + f"Tool execution failed. Errors: {[tr.content for tr in tool_results if 'Error' in str(tr.content)]}" + ) + elif executed_tool_names: # If any tool was called + current_task["status"] = "completed" + current_task["result_summary"] = ( + f"Executed tool(s): {', '.join(executed_tool_names)}." + ) + # TODO: Could ask LLM to summarize the tool_results for this task if needed, rather than just listing tools. + else: # No tool calls but AI response had .tool_calls structure (empty) + current_task["status"] = "failed" # Or a more specific status + current_task["result_summary"] = "LLM prepared for tool call but provided no tools." + + # Save progress + _save_plan_to_md(plan, output_dir) + _save_search_results_to_json(current_search_results, output_dir) + + # Determine next indices + next_task_idx = task_idx + 1 + next_cat_idx = cat_idx + if next_task_idx >= len(current_category["tasks"]): + next_cat_idx += 1 + next_task_idx = 0 + + updated_messages = ( + state["messages"] + current_task_message_history + [ai_response] + tool_results + ) + + return { + "research_plan": plan, + "search_results": current_search_results, + "current_category_index": next_cat_idx, + "current_task_index_in_category": next_task_idx, + "messages": updated_messages, + } + + except Exception as e: + logger.error( + f"Unhandled error during research execution for task '{current_task['task_description']}': {e}", + exc_info=True, + ) + current_task["status"] = "failed" + _save_plan_to_md(plan, output_dir) + # Determine next indices even on error to attempt to move on + next_task_idx = task_idx + 1 + next_cat_idx = cat_idx + if next_task_idx >= len(current_category["tasks"]): + next_cat_idx += 1 + next_task_idx = 0 + return { + "research_plan": plan, + "current_category_index": next_cat_idx, + "current_task_index_in_category": next_task_idx, + "error_message": f"Core Execution Error on task '{current_task['task_description']}': {e}", + "messages": state["messages"] + + current_task_message_history, # Preserve messages up to error + } + + +async def synthesis_node(state: DeepResearchState) -> dict[str, Any]: + """Synthesizes the final report from the collected search results.""" + logger.info("--- Entering Synthesis Node ---") + if state.get("stop_requested"): + logger.info("Stop requested, skipping synthesis.") + return {"stop_requested": True} + + llm = state["llm"] + topic = state["topic"] + search_results = state.get("search_results", []) + output_dir = state["output_dir"] + plan = state["research_plan"] # Include plan for context + + if not search_results: + logger.warning("No search results found to synthesize report.") + report = f"# Research Report: {topic}\n\nNo information was gathered during the research process." + _save_report_to_md(report, output_dir) + return {"final_report": report} + + logger.info(f"Synthesizing report from {len(search_results)} collected search result entries.") + + # Prepare context for the LLM + # Format search results nicely, maybe group by query or original plan step + formatted_results = "" + references = {} + for _i, result_entry in enumerate(search_results): + query = result_entry.get("query", "Unknown Query") # From parallel_browser_search + tool_name = result_entry.get("tool_name") # From other tools + status = result_entry.get("status", "unknown") + result_data = result_entry.get("result") # From BrowserUseAgent's final_result + tool_output_str = result_entry.get("output") # From other tools + + if tool_name == "parallel_browser_search" and status == "completed" and result_data: + # result_data is the summary from BrowserUseAgent + formatted_results += f'### Finding from Web Search Query: "{query}"\n' + formatted_results += ( + f"- **Summary:**\n{result_data}\n" # result_data is already a summary string here + ) + # If result_data contained title/URL, you'd format them here. + # The current BrowserUseAgent returns a string summary directly as 'final_data' in run_single_browser_task + formatted_results += "---\n" + elif tool_name != "parallel_browser_search" and status == "completed" and tool_output_str: + formatted_results += ( + f'### Finding from Tool: "{tool_name}" (Args: {result_entry.get("args")})\n' + ) + formatted_results += f"- **Output:**\n{tool_output_str}\n" + formatted_results += "---\n" + elif status == "failed": + error = result_entry.get("error") + q_or_t = f'Query: "{query}"' if query != "Unknown Query" else f'Tool: "{tool_name}"' + formatted_results += f"### Failed {q_or_t}\n" + formatted_results += f"- **Error:** {error}\n" + formatted_results += "---\n" + + # Prepare the research plan context + plan_summary = "\nResearch Plan Followed:\n" + for cat_idx, category in enumerate(plan): + plan_summary += f"\n#### Category {cat_idx + 1}: {category['category_name']}\n" + for _task_idx, task in enumerate(category["tasks"]): + marker = ( + "[x]" + if task["status"] == "completed" + else "[ ]" + if task["status"] == "pending" + else "[-]" + ) + plan_summary += f" - {marker} {task['task_description']}\n" + + synthesis_prompt = ChatPromptTemplate.from_messages( + [ + ( + "system", + """You are a professional researcher tasked with writing a comprehensive and well-structured report based on collected findings. + The report should address the research topic thoroughly, synthesizing the information gathered from various sources. + Structure the report logically: + 1. Briefly introduce the topic and the report's scope (mentioning the research plan followed, including categories and tasks, is good). + 2. Discuss the key findings, organizing them thematically, possibly aligning with the research categories. Analyze, compare, and contrast information. + 3. Summarize the main points and offer concluding thoughts. + + Ensure the tone is objective and professional. + If findings are contradictory or incomplete, acknowledge this. + """, # Removed citation part for simplicity for now, as browser agent returns summaries. + ), + ( + "human", + f""" + **Research Topic:** {topic} + + {plan_summary} + + **Collected Findings:** + ``` + {formatted_results} + ``` + + Please generate the final research report in Markdown format based **only** on the information above. + """, + ), + ] + ) + + try: + response = await llm.ainvoke( + synthesis_prompt.format_prompt( + topic=topic, + plan_summary=plan_summary, + formatted_results=formatted_results, + ).to_messages() + ) + final_report_md = response.content + + # Append the reference list automatically to the end of the generated markdown + if references: + report_references_section = "\n\n## References\n\n" + # Sort refs by ID for consistent output + sorted_refs = sorted(references.values(), key=lambda x: x["id"]) + for ref in sorted_refs: + report_references_section += f"[{ref['id']}] {ref['title']} - {ref['url']}\n" + final_report_md += report_references_section + + logger.info("Successfully synthesized the final report.") + _save_report_to_md(final_report_md, output_dir) + return {"final_report": final_report_md} + + except Exception as e: + logger.error(f"Error during report synthesis: {e}", exc_info=True) + return {"error_message": f"LLM Error during synthesis: {e}"} + + +# --- Langgraph Edges and Conditional Logic --- + + +def should_continue(state: DeepResearchState) -> str: + logger.info("--- Evaluating Condition: Should Continue? ---") + if state.get("stop_requested"): + logger.info("Stop requested, routing to END.") + return "end_run" + if state.get("error_message") and "Core Execution Error" in ( + state["error_message"] or "" + ): # Critical error in node + logger.warning(f"Critical error detected: {state['error_message']}. Routing to END.") + return "end_run" + + plan = state.get("research_plan") + cat_idx = state.get("current_category_index", 0) + task_idx = state.get("current_task_index_in_category", 0) # This is the *next* task to check + + if not plan: + logger.warning("No research plan found. Routing to END.") + return "end_run" + + # Check if the current indices point to a valid pending task + if cat_idx < len(plan): + current_category = plan[cat_idx] + if task_idx < len(current_category["tasks"]): + # We are trying to execute the task at plan[cat_idx]["tasks"][task_idx] + # The research_execution_node will handle if it's already completed. + logger.info( + f"Plan has potential pending tasks (next up: Category {cat_idx}, Task {task_idx}). Routing to Research Execution." + ) + return "execute_research" + else: # task_idx is out of bounds for current category, means we need to check next category + if cat_idx + 1 < len(plan): # If there is a next category + logger.info( + f"Finished tasks in category {cat_idx}. Moving to category {cat_idx + 1}. Routing to Research Execution." + ) + # research_execution_node will update state to {current_category_index: cat_idx + 1, current_task_index_in_category: 0} + # Or rather, the previous execution node already set these indices to the start of the next category. + return "execute_research" + + # If we've gone through all categories and tasks (cat_idx >= len(plan)) + logger.info( + "All plan categories and tasks processed or current indices are out of bounds. Routing to Synthesis." + ) + return "synthesize_report" + + +# --- DeepSearchAgent Class --- + + +class DeepResearchAgent: + def __init__( + self, + llm: Any, + browser_config: dict[str, Any], + mcp_server_config: dict[str, Any] | None = None, + ): + """ + Initializes the DeepSearchAgent. + + Args: + llm: The Langchain compatible language model instance. + browser_config: Configuration dictionary for the BrowserUseAgent tool. + Example: {"headless": True, "window_width": 1280, ...} + mcp_server_config: Optional configuration for the MCP client. + """ + self.llm = llm + self.browser_config = browser_config + self.mcp_server_config = mcp_server_config + self.mcp_client = None + self.stopped = False + self.graph = self._compile_graph() + self.current_task_id: str | None = None + self.stop_event: threading.Event | None = None + self.runner: asyncio.Task | None = None # To hold the asyncio task for run + + async def _setup_tools( + self, task_id: str, stop_event: threading.Event, max_parallel_browsers: int = 1 + ) -> list[Tool]: + """Sets up the basic tools (File I/O) and optional MCP tools.""" + tools = [ + WriteFileTool(), + ReadFileTool(), + ListDirectoryTool(), + ] # Basic file operations + browser_use_tool = create_browser_search_tool( + llm=self.llm, + browser_config=self.browser_config, + task_id=task_id, + stop_event=stop_event, + max_parallel_browsers=max_parallel_browsers, + ) + tools += [browser_use_tool] + # Add MCP tools if config is provided + if self.mcp_server_config: + try: + logger.info("Setting up MCP client and tools...") + if not self.mcp_client: + self.mcp_client = await setup_mcp_client_and_tools(self.mcp_server_config) + + if self.mcp_client: + mcp_tools = await self.mcp_client.get_tools() + logger.info(f"Loaded {len(mcp_tools)} MCP tools from MCP servers") + + # Log each MCP tool for visibility + for tool in mcp_tools: + logger.info(f" ✓ MCP Tool: {tool.name} - {tool.description[:80]}...") + + tools.extend(mcp_tools) + else: + logger.warning("MCP client setup returned None") + except Exception as e: + logger.error(f"Failed to set up MCP tools: {e}", exc_info=True) + + # Remove duplicates by name (keep last occurrence) + tools_map = {tool.name: tool for tool in tools} + final_tools = list(tools_map.values()) + + logger.info(f"Total tools available: {len(final_tools)}") + logger.info(" - File tools: 3 (write_file, read_file, list_directory)") + logger.info(" - Browser tool: 1 (parallel_browser_search)") + logger.info(f" - MCP tools: {len(final_tools) - 4}") + + return final_tools + + async def close_mcp_client(self): + if self.mcp_client: + await self.mcp_client.__aexit__(None, None, None) + self.mcp_client = None + + async def get_mcp_tools_summary(self) -> str: + """ + Get a summary of available MCP tools. + + Returns: + Human-readable string describing available MCP tools + """ + if not self.mcp_client: + return "No MCP tools loaded." + + try: + mcp_tools = await self.mcp_client.get_tools() + if not mcp_tools: + return "No MCP tools available." + + # Group tools by server name (extract from tool name) + tools_by_server = {} + for tool in mcp_tools: + # Try to extract server name from tool name + parts = tool.name.split(".", 2) + if len(parts) >= 2: + server_name = parts[0] if parts[0] != "mcp" else parts[1] + if server_name not in tools_by_server: + tools_by_server[server_name] = [] + tools_by_server[server_name].append(tool.name) + else: + if "other" not in tools_by_server: + tools_by_server["other"] = [] + tools_by_server["other"].append(tool.name) + + lines = [f"Available MCP Tools ({len(mcp_tools)} total):"] + for server_name, tool_names in tools_by_server.items(): + lines.append(f"\n 📦 {server_name} ({len(tool_names)} tools):") + for tool_name in tool_names: + lines.append(f" - {tool_name}") + + return "\n".join(lines) + except Exception as e: + logger.error(f"Error getting MCP tools summary: {e}") + return f"Error retrieving MCP tools: {e}" + + def _compile_graph(self): + """Compiles the Langgraph state machine.""" + workflow = StateGraph(DeepResearchState) + + # Add nodes + workflow.add_node("plan_research", planning_node) + workflow.add_node("execute_research", research_execution_node) + workflow.add_node("synthesize_report", synthesis_node) + workflow.add_node( + "end_run", lambda state: logger.info("--- Reached End Run Node ---") or {} + ) # Simple end node + + # Define edges + workflow.set_entry_point("plan_research") + + workflow.add_edge("plan_research", "execute_research") # Always execute after planning + + # Conditional edge after execution + workflow.add_conditional_edges( + "execute_research", + should_continue, + { + "execute_research": "execute_research", # Loop back if more steps + "synthesize_report": "synthesize_report", # Move to synthesis if done + "end_run": "end_run", # End if stop requested or error + }, + ) + + workflow.add_edge("synthesize_report", "end_run") # End after synthesis + + app = workflow.compile() + return app + + async def run( + self, + topic: str, + task_id: str | None = None, + save_dir: str = "./tmp/deep_research", + max_parallel_browsers: int = 1, + ) -> dict[str, Any]: + """ + Starts the deep research process (Async Generator Version). + + Args: + topic: The research topic. + task_id: Optional existing task ID to resume. If None, a new ID is generated. + + Yields: + Intermediate state updates or messages during execution. + """ + if self.runner and not self.runner.done(): + logger.warning("Agent is already running. Please stop the current task first.") + # Return an error status instead of yielding + return { + "status": "error", + "message": "Agent already running.", + "task_id": self.current_task_id, + } + + self.current_task_id = task_id if task_id else str(uuid.uuid4()) + safe_root_dir = "./tmp/deep_research" + normalized_save_dir = os.path.normpath(save_dir) + if not normalized_save_dir.startswith(os.path.abspath(safe_root_dir)): + logger.warning(f"Unsafe save_dir detected: {save_dir}. Using default directory.") + normalized_save_dir = os.path.abspath(safe_root_dir) + output_dir = os.path.join(normalized_save_dir, self.current_task_id) + os.makedirs(output_dir, exist_ok=True) + + logger.info( + f"[AsyncGen] Starting research task ID: {self.current_task_id} for topic: '{topic}'" + ) + logger.info(f"[AsyncGen] Output directory: {output_dir}") + + self.stop_event = threading.Event() + _AGENT_STOP_FLAGS[self.current_task_id] = self.stop_event + agent_tools = await self._setup_tools( + self.current_task_id, self.stop_event, max_parallel_browsers + ) + + # Log available MCP tools + mcp_tools_summary = await self.get_mcp_tools_summary() + if "No MCP tools" not in mcp_tools_summary: + logger.info(f"\n{mcp_tools_summary}") + initial_state: DeepResearchState = { + "task_id": self.current_task_id, + "topic": topic, + "research_plan": [], + "search_results": [], + "messages": [], + "llm": self.llm, + "tools": agent_tools, + "output_dir": Path(output_dir), + "browser_config": self.browser_config, + "final_report": None, + "current_category_index": 0, + "current_task_index_in_category": 0, + "stop_requested": False, + "error_message": None, + } + + if task_id: + logger.info(f"Attempting to resume task {task_id}...") + loaded_state = _load_previous_state(task_id, output_dir) + initial_state.update(loaded_state) + if loaded_state.get("research_plan"): + logger.info( + f"Resuming with {len(loaded_state['research_plan'])} plan categories " + f"and {len(loaded_state.get('search_results', []))} existing results. " + f"Next task: Cat {initial_state['current_category_index']}, Task {initial_state['current_task_index_in_category']}" + ) + initial_state["topic"] = ( + topic # Allow overriding topic even when resuming? Or use stored topic? Let's use new one. + ) + else: + logger.warning( + f"Resume requested for {task_id}, but no previous plan found. Starting fresh." + ) + + # --- Execute Graph using ainvoke --- + final_state = None + status = "unknown" + message = None + try: + logger.info(f"Invoking graph execution for task {self.current_task_id}...") + self.runner = asyncio.create_task(self.graph.ainvoke(initial_state)) + final_state = await self.runner + logger.info(f"Graph execution finished for task {self.current_task_id}.") + + # Determine status based on final state + if self.stop_event and self.stop_event.is_set(): + status = "stopped" + message = "Research process was stopped by request." + logger.info(message) + elif final_state and final_state.get("error_message"): + status = "error" + message = final_state["error_message"] + logger.error(f"Graph execution completed with error: {message}") + elif final_state and final_state.get("final_report"): + status = "completed" + message = "Research process completed successfully." + logger.info(message) + else: + # If it ends without error/report (e.g., empty plan, stopped before synthesis) + status = "finished_incomplete" + message = ( + "Research process finished, but may be incomplete (no final report generated)." + ) + logger.warning(message) + + except asyncio.CancelledError: + status = "cancelled" + message = f"Agent run task cancelled for {self.current_task_id}." + logger.info(message) + # final_state will remain None or the state before cancellation if checkpointing was used + except Exception as e: + status = "error" + message = f"Unhandled error during graph execution for {self.current_task_id}: {e}" + logger.error(message, exc_info=True) + # final_state will remain None or the state before the error + finally: + logger.info(f"Cleaning up resources for task {self.current_task_id}") + task_id_to_clean = self.current_task_id + + self.stop_event = None + self.current_task_id = None + self.runner = None # Mark runner as finished + if self.mcp_client: + await self.mcp_client.__aexit__(None, None, None) + + # Return a result dictionary including the status and the final state if available + return { + "status": status, + "message": message, + "task_id": task_id_to_clean, # Use the stored task_id + "final_state": final_state if final_state else {}, # Return the final state dict + } + + async def _stop_lingering_browsers(self, task_id): + """Attempts to stop any BrowserUseAgent instances associated with the task_id.""" + keys_to_stop = [key for key in _BROWSER_AGENT_INSTANCES if key.startswith(f"{task_id}_")] + if not keys_to_stop: + return + + logger.warning( + f"Found {len(keys_to_stop)} potentially lingering browser agents for task {task_id}. Attempting stop..." + ) + for key in keys_to_stop: + agent_instance = _BROWSER_AGENT_INSTANCES.get(key) + try: + if agent_instance: + # Assuming BU agent has an async stop method + await agent_instance.stop() + logger.info(f"Called stop() on browser agent instance {key}") + except Exception as e: + logger.error(f"Error calling stop() on browser agent instance {key}: {e}") + + async def stop(self): + """Signals the currently running agent task to stop.""" + if not self.current_task_id or not self.stop_event: + logger.info("No agent task is currently running.") + return + + logger.info(f"Stop requested for task ID: {self.current_task_id}") + self.stop_event.set() # Signal the stop event + self.stopped = True + await self._stop_lingering_browsers(self.current_task_id) + + def close(self): + self.stopped = False diff --git a/src/web_ui/browser/__init__.py b/src/web_ui/browser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/web_ui/browser/custom_browser.py b/src/web_ui/browser/custom_browser.py new file mode 100644 index 00000000..423da8e5 --- /dev/null +++ b/src/web_ui/browser/custom_browser.py @@ -0,0 +1,106 @@ + +import logging +import socket + +from browser_use.browser.browser import IN_DOCKER, Browser +from browser_use.browser.chrome import ( + CHROME_ARGS, + CHROME_DETERMINISTIC_RENDERING_ARGS, + CHROME_DISABLE_SECURITY_ARGS, + CHROME_DOCKER_ARGS, + CHROME_HEADLESS_ARGS, +) +from browser_use.browser.context import BrowserContextConfig +from browser_use.browser.utils.screen_resolution import ( + get_screen_resolution, + get_window_adjustments, +) +from playwright.async_api import Browser as PlaywrightBrowser +from playwright.async_api import ( + Playwright, +) + +from .custom_context import CustomBrowserContext + +logger = logging.getLogger(__name__) + + +class CustomBrowser(Browser): + async def new_context(self, config: BrowserContextConfig | None = None) -> CustomBrowserContext: + """Create a browser context""" + browser_config = self.config.model_dump() if self.config else {} + context_config = config.model_dump() if config else {} + merged_config = {**browser_config, **context_config} + return CustomBrowserContext(config=BrowserContextConfig(**merged_config), browser=self) + + async def _setup_builtin_browser(self, playwright: Playwright) -> PlaywrightBrowser: + """Sets up and returns a Playwright Browser instance with anti-detection measures.""" + assert self.config.browser_binary_path is None, ( + "browser_binary_path should be None if trying to use the builtin browsers" + ) + + # Use the configured window size from new_context_config if available + if ( + not self.config.headless + and hasattr(self.config, "new_context_config") + and hasattr(self.config.new_context_config, "window_width") + and hasattr(self.config.new_context_config, "window_height") + ): + screen_size = { + "width": self.config.new_context_config.window_width, + "height": self.config.new_context_config.window_height, + } + offset_x, offset_y = get_window_adjustments() + elif self.config.headless: + screen_size = {"width": 1920, "height": 1080} + offset_x, offset_y = 0, 0 + else: + screen_size = get_screen_resolution() + offset_x, offset_y = get_window_adjustments() + + chrome_args = { + f"--remote-debugging-port={self.config.chrome_remote_debugging_port}", + *CHROME_ARGS, + *(CHROME_DOCKER_ARGS if IN_DOCKER else []), + *(CHROME_HEADLESS_ARGS if self.config.headless else []), + *(CHROME_DISABLE_SECURITY_ARGS if self.config.disable_security else []), + *(CHROME_DETERMINISTIC_RENDERING_ARGS if self.config.deterministic_rendering else []), + f"--window-position={offset_x},{offset_y}", + f"--window-size={screen_size['width']},{screen_size['height']}", + *self.config.extra_browser_args, + } + + # check if chrome remote debugging port is already taken, + # if so remove the remote-debugging-port arg to prevent conflicts + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + if s.connect_ex(("localhost", self.config.chrome_remote_debugging_port)) == 0: + chrome_args.remove( + f"--remote-debugging-port={self.config.chrome_remote_debugging_port}" + ) + + browser_class = getattr(playwright, self.config.browser_class) + args = { + "chromium": list(chrome_args), + "firefox": [ + *{ + "-no-remote", + *self.config.extra_browser_args, + } + ], + "webkit": [ + *{ + "--no-startup-window", + *self.config.extra_browser_args, + } + ], + } + + browser = await browser_class.launch( + channel="chromium", # https://github.com/microsoft/playwright/issues/33566 + headless=self.config.headless, + args=args[self.config.browser_class], + proxy=self.config.proxy.model_dump() if self.config.proxy else None, + handle_sigterm=False, + handle_sigint=False, + ) + return browser diff --git a/src/web_ui/browser/custom_context.py b/src/web_ui/browser/custom_context.py new file mode 100644 index 00000000..dd3d2a09 --- /dev/null +++ b/src/web_ui/browser/custom_context.py @@ -0,0 +1,16 @@ +import logging + +from browser_use.browser.browser import Browser +from browser_use.browser.context import BrowserContext, BrowserContextConfig, BrowserContextState + +logger = logging.getLogger(__name__) + + +class CustomBrowserContext(BrowserContext): + def __init__( + self, + browser: Browser, + config: BrowserContextConfig | None = None, + state: BrowserContextState | None = None, + ): + super().__init__(browser=browser, config=config, state=state) diff --git a/src/web_ui/controller/__init__.py b/src/web_ui/controller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/web_ui/controller/custom_controller.py b/src/web_ui/controller/custom_controller.py new file mode 100644 index 00000000..b85688b2 --- /dev/null +++ b/src/web_ui/controller/custom_controller.py @@ -0,0 +1,276 @@ +import inspect +import logging +import os +from collections.abc import Awaitable, Callable +from typing import Any, TypeVar + +from browser_use.agent.views import ActionModel, ActionResult +from browser_use.browser.context import BrowserContext +from browser_use.controller.registry.service import RegisteredAction +from browser_use.controller.service import Controller +from browser_use.utils import time_execution_sync +from langchain_core.language_models.chat_models import BaseChatModel +from pydantic import BaseModel + +from src.web_ui.utils.mcp_client import create_tool_param_model, setup_mcp_client_and_tools +from src.web_ui.utils.mcp_config import load_mcp_config + +logger = logging.getLogger(__name__) + +Context = TypeVar("Context") + + +class CustomController(Controller): + def __init__( + self, + exclude_actions: list[str] | None = None, + output_model: type[BaseModel] | None = None, + ask_assistant_callback: Callable[[str, BrowserContext], dict[str, Any]] + | Callable[[str, BrowserContext], Awaitable[dict[str, Any]]] + | None = None, + ): + if exclude_actions is None: + exclude_actions = [] + super().__init__(exclude_actions=exclude_actions, output_model=output_model) + self._register_custom_actions() + self.ask_assistant_callback = ask_assistant_callback + self.mcp_client = None + self.mcp_server_config = None + + def _register_custom_actions(self): + """Register all custom browser actions""" + + @self.registry.action( + "When executing tasks, prioritize autonomous completion. However, if you encounter a definitive blocker " + "that prevents you from proceeding independently – such as needing credentials you don't possess, " + "requiring subjective human judgment, needing a physical action performed, encountering complex CAPTCHAs, " + "or facing limitations in your capabilities – you must request human assistance." + ) + async def ask_for_assistant(query: str, browser: BrowserContext): + if self.ask_assistant_callback: + if inspect.iscoroutinefunction(self.ask_assistant_callback): + user_response = await self.ask_assistant_callback(query, browser) + else: + user_response = self.ask_assistant_callback(query, browser) + msg = f"AI ask: {query}. User response: {user_response['response']}" + logger.info(msg) + return ActionResult(extracted_content=msg, include_in_memory=True) + else: + return ActionResult( + extracted_content="Human cannot help you. Please try another way.", + include_in_memory=True, + ) + + @self.registry.action( + "Upload file to interactive element with file path ", + ) + async def upload_file( + index: int, path: str, browser: BrowserContext, available_file_paths: list[str] + ): + if path not in available_file_paths: + return ActionResult(error=f"File path {path} is not available") + + if not os.path.exists(path): + return ActionResult(error=f"File {path} does not exist") + + dom_el = await browser.get_dom_element_by_index(index) + + file_upload_dom_el = dom_el.get_file_upload_element() + + if file_upload_dom_el is None: + msg = f"No file upload element found at index {index}" + logger.info(msg) + return ActionResult(error=msg) + + file_upload_el = await browser.get_locate_element(file_upload_dom_el) + + if file_upload_el is None: + msg = f"No file upload element found at index {index}" + logger.info(msg) + return ActionResult(error=msg) + + try: + await file_upload_el.set_input_files(path) + msg = f"Successfully uploaded file to index {index}" + logger.info(msg) + return ActionResult(extracted_content=msg, include_in_memory=True) + except Exception as e: + msg = f"Failed to upload file to index {index}: {str(e)}" + logger.info(msg) + return ActionResult(error=msg) + + @time_execution_sync("--act") + async def act( + self, + action: ActionModel, + browser_context: BrowserContext | None = None, + # + page_extraction_llm: BaseChatModel | None = None, + sensitive_data: dict[str, str] | None = None, + available_file_paths: list[str] | None = None, + # + context: Context | None = None, + ) -> ActionResult: + """Execute an action""" + + try: + for action_name, params in action.model_dump(exclude_unset=True).items(): + if params is not None: + if action_name.startswith("mcp"): + # this is a mcp tool + logger.debug(f"Invoke MCP tool: {action_name}") + mcp_tool = self.registry.registry.actions.get(action_name).function + result = await mcp_tool.ainvoke(params) + else: + result = await self.registry.execute_action( + action_name, + params, + browser=browser_context, + page_extraction_llm=page_extraction_llm, + sensitive_data=sensitive_data, + available_file_paths=available_file_paths, + context=context, + ) + + if isinstance(result, str): + return ActionResult(extracted_content=result) + elif isinstance(result, ActionResult): + return result + elif result is None: + return ActionResult() + else: + raise ValueError(f"Invalid action result type: {type(result)} of {result}") + return ActionResult() + except Exception as e: + raise e + + async def setup_mcp_client(self, mcp_server_config: dict[str, Any] | None = None): + """ + Setup MCP client with provided config or auto-load from data/mcp.json. + + Args: + mcp_server_config: Optional MCP server configuration dict. + If None, attempts to load from data/mcp.json file. + """ + # If no config provided, try to load from file + if mcp_server_config is None: + logger.info("No MCP config provided, attempting to load from data/mcp.json") + mcp_server_config = load_mcp_config() + + if mcp_server_config is None: + logger.info("No MCP configuration file found. MCP tools will not be available.") + return + + self.mcp_server_config = mcp_server_config + + # Setup client and register tools + if self.mcp_server_config: + self.mcp_client = await setup_mcp_client_and_tools(self.mcp_server_config) + if self.mcp_client: + await self.register_mcp_tools() + logger.info("MCP client setup completed successfully") + else: + logger.warning("MCP client setup failed") + + async def register_mcp_tools(self): + """ + Register the MCP tools used by this controller. + Uses the new langchain-mcp-adapters 0.1.0+ API. + """ + if self.mcp_client and self.mcp_server_config: + try: + # Get all server names from the config + if "mcpServers" in self.mcp_server_config: + server_names = list(self.mcp_server_config["mcpServers"].keys()) + else: + server_names = list(self.mcp_server_config.keys()) + + total_tools = 0 + for server_name in server_names: + # Get tools for each server individually + tools = await self.mcp_client.get_tools(server_name=server_name) + + for tool in tools: + tool_name = f"mcp.{server_name}.{tool.name}" + param_model_class = create_tool_param_model(tool) + self.registry.registry.actions[tool_name] = RegisteredAction( + name=tool_name, + description=tool.description, + function=tool, + param_model=param_model_class, + ) + logger.info(f"Add mcp tool: {tool_name}") + logger.debug(f"Registered {len(tools)} mcp tools for {server_name}") + total_tools += len(tools) + + logger.info( + f"Successfully registered {total_tools} MCP tools from {len(server_names)} servers" + ) + except Exception as e: + logger.error(f"Failed to register MCP tools: {e}", exc_info=True) + else: + logger.warning("MCP client not started.") + + async def close_mcp_client(self): + """Close MCP client and cleanup resources.""" + if self.mcp_client: + try: + await self.mcp_client.__aexit__(None, None, None) + logger.info("MCP client closed successfully") + except Exception as e: + logger.error(f"Error closing MCP client: {e}", exc_info=True) + finally: + self.mcp_client = None + + async def reload_mcp_client(self, mcp_server_config: dict[str, Any] | None = None): + """ + Reload MCP client with new configuration. + + This closes the existing client and sets up a new one. + + Args: + mcp_server_config: Optional new MCP server configuration dict. + If None, reloads from data/mcp.json file. + """ + logger.info("Reloading MCP client...") + + # Close existing client + await self.close_mcp_client() + + # Unregister existing MCP tools + if self.registry and hasattr(self.registry, "registry"): + tools_to_remove = [ + name for name in self.registry.registry.actions.keys() if name.startswith("mcp.") + ] + for tool_name in tools_to_remove: + del self.registry.registry.actions[tool_name] + logger.debug(f"Removed MCP tool: {tool_name}") + + # Setup new client + await self.setup_mcp_client(mcp_server_config) + logger.info("MCP client reload completed") + + def get_registered_mcp_tools(self) -> dict[str, list[str]]: + """ + Get list of currently registered MCP tools grouped by server. + + Returns: + Dictionary mapping server names to lists of tool names + """ + tools_by_server = {} + + if self.registry and hasattr(self.registry, "registry"): + for tool_name in self.registry.registry.actions.keys(): + if tool_name.startswith("mcp."): + # Parse tool name: mcp.{server_name}.{tool_name} + parts = tool_name.split(".", 2) + if len(parts) >= 3: + server_name = parts[1] + actual_tool_name = parts[2] + + if server_name not in tools_by_server: + tools_by_server[server_name] = [] + + tools_by_server[server_name].append(actual_tool_name) + + return tools_by_server diff --git a/src/web_ui/events/__init__.py b/src/web_ui/events/__init__.py new file mode 100644 index 00000000..9cad5d13 --- /dev/null +++ b/src/web_ui/events/__init__.py @@ -0,0 +1,21 @@ +""" +Event-driven architecture components. +""" + +from src.web_ui.events.event_bus import ( + Event, + EventBus, + EventHandler, + EventType, + create_event, + get_event_bus, +) + +__all__ = [ + "Event", + "EventBus", + "EventHandler", + "EventType", + "get_event_bus", + "create_event", +] diff --git a/src/web_ui/events/event_bus.py b/src/web_ui/events/event_bus.py new file mode 100644 index 00000000..54bc9bc1 --- /dev/null +++ b/src/web_ui/events/event_bus.py @@ -0,0 +1,232 @@ +""" +Event-driven architecture for scalable agent execution. +""" + +import asyncio +import logging +import os +import time +from collections.abc import Awaitable, Callable +from dataclasses import dataclass +from enum import Enum +from typing import Any + +logger = logging.getLogger(__name__) + + +class EventType(str, Enum): + """All event types in the system.""" + + # Agent lifecycle + AGENT_START = "agent.start" + AGENT_STEP = "agent.step" + AGENT_COMPLETE = "agent.complete" + AGENT_ERROR = "agent.error" + AGENT_PAUSED = "agent.paused" + AGENT_RESUMED = "agent.resumed" + + # LLM events + LLM_REQUEST = "llm.request" + LLM_TOKEN = "llm.token" + LLM_RESPONSE = "llm.response" + LLM_ERROR = "llm.error" + + # Browser events + ACTION_START = "action.start" + ACTION_COMPLETE = "action.complete" + ACTION_ERROR = "action.error" + BROWSER_NAVIGATE = "browser.navigate" + BROWSER_SCREENSHOT = "browser.screenshot" + + # Trace events + TRACE_SPAN_START = "trace.span.start" + TRACE_SPAN_END = "trace.span.end" + TRACE_COMPLETE = "trace.complete" + + # UI events + UI_CONNECTED = "ui.connected" + UI_DISCONNECTED = "ui.disconnected" + UI_COMMAND = "ui.command" + + # Workflow events + WORKFLOW_NODE_START = "workflow.node.start" + WORKFLOW_NODE_COMPLETE = "workflow.node.complete" + WORKFLOW_EDGE_TRAVERSED = "workflow.edge.traversed" + + +@dataclass +class Event: + """Base event class.""" + + event_type: EventType + session_id: str + timestamp: float + data: dict[str, Any] + correlation_id: str | None = None # For tracing related events + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary.""" + return { + "event_type": self.event_type.value, + "session_id": self.session_id, + "timestamp": self.timestamp, + "data": self.data, + "correlation_id": self.correlation_id, + } + + +EventHandler = Callable[[Event], Awaitable[None]] + + +class EventBus: + """ + Event bus for publish-subscribe pattern. + Supports both in-memory and Redis backends. + """ + + def __init__(self, backend: str = "memory"): + self.backend = backend + self._subscribers: dict[EventType, set[EventHandler]] = {} + self._lock = asyncio.Lock() + self._event_queue: asyncio.Queue = asyncio.Queue() + self._processing_task: asyncio.Task | None = None + + if backend == "redis": + self._init_redis() + + def _init_redis(self): + """Initialize Redis pub/sub.""" + try: + import redis.asyncio as redis # type: ignore[import-untyped] + + self.redis = redis.Redis( + host=os.getenv("REDIS_HOST", "localhost"), + port=int(os.getenv("REDIS_PORT", 6379)), + decode_responses=True, + ) + logger.info("Redis event bus initialized") + except ImportError: + logger.warning("redis package not installed, falling back to memory") + self.backend = "memory" + except Exception as e: + logger.error(f"Failed to initialize Redis: {e}") + self.backend = "memory" + + async def subscribe(self, event_type: EventType, handler: EventHandler): + """Subscribe to an event type.""" + async with self._lock: + if event_type not in self._subscribers: + self._subscribers[event_type] = set() + self._subscribers[event_type].add(handler) + logger.debug(f"Subscribed to {event_type.value}") + + async def unsubscribe(self, event_type: EventType, handler: EventHandler): + """Unsubscribe from an event type.""" + async with self._lock: + if event_type in self._subscribers: + self._subscribers[event_type].discard(handler) + logger.debug(f"Unsubscribed from {event_type.value}") + + async def publish(self, event: Event): + """Publish an event to all subscribers.""" + logger.debug(f"Publishing {event.event_type.value} for session {event.session_id}") + + if self.backend == "redis": + await self._publish_redis(event) + else: + await self._publish_memory(event) + + async def _publish_memory(self, event: Event): + """Publish to in-memory subscribers.""" + if event.event_type in self._subscribers: + handlers = list(self._subscribers[event.event_type]) + + # Call handlers concurrently + await asyncio.gather( + *[self._safe_handle(handler, event) for handler in handlers], + return_exceptions=True, + ) + + async def _publish_redis(self, event: Event): + """Publish to Redis pub/sub.""" + import json + + channel = f"events:{event.event_type.value}" + message = json.dumps(event.to_dict()) + + try: + await self.redis.publish(channel, message) + except Exception as e: + logger.error(f"Failed to publish to Redis: {e}") + + async def _safe_handle(self, handler: EventHandler, event: Event): + """Call handler with error handling.""" + try: + await handler(event) + except Exception as e: + logger.error(f"Error in event handler for {event.event_type.value}: {e}", exc_info=True) + + async def start_processing(self): + """Start background event processing.""" + if self._processing_task is None: + self._processing_task = asyncio.create_task(self._process_events()) + logger.info("Event bus processing started") + + async def stop_processing(self): + """Stop background event processing.""" + if self._processing_task: + self._processing_task.cancel() + try: + await self._processing_task + except asyncio.CancelledError: + pass + self._processing_task = None + logger.info("Event bus processing stopped") + + async def _process_events(self): + """Process events from queue.""" + while True: + try: + event = await self._event_queue.get() + await self.publish(event) + except asyncio.CancelledError: + break + except Exception as e: + logger.error(f"Error processing event: {e}") + + async def close(self): + """Clean up resources.""" + await self.stop_processing() + + if self.backend == "redis" and hasattr(self, "redis"): + await self.redis.close() + logger.info("Redis connection closed") + + +# Global event bus instance +_event_bus: EventBus | None = None + + +def get_event_bus() -> EventBus: + """Get the global event bus instance.""" + global _event_bus + if _event_bus is None: + backend = os.getenv("EVENT_BUS_BACKEND", "memory") + _event_bus = EventBus(backend=backend) + return _event_bus + + +def create_event( + event_type: EventType, + session_id: str, + data: dict[str, Any], + correlation_id: str | None = None, +) -> Event: + """Helper to create an event with current timestamp.""" + return Event( + event_type=event_type, + session_id=session_id, + timestamp=time.time(), + data=data, + correlation_id=correlation_id, + ) diff --git a/src/web_ui/observability/__init__.py b/src/web_ui/observability/__init__.py new file mode 100644 index 00000000..7ad3d22b --- /dev/null +++ b/src/web_ui/observability/__init__.py @@ -0,0 +1,26 @@ +""" +Observability and tracing utilities for agent execution. +""" + +from src.web_ui.observability.cost_calculator import ( + calculate_llm_cost, + estimate_task_cost, + format_cost, + get_pricing_info, +) +from src.web_ui.observability.trace_models import ExecutionTrace, SpanType, TraceSpan +from src.web_ui.observability.tracer import AgentTracer + +__all__ = [ + # Tracer + "AgentTracer", + # Models + "ExecutionTrace", + "TraceSpan", + "SpanType", + # Cost calculation + "calculate_llm_cost", + "estimate_task_cost", + "get_pricing_info", + "format_cost", +] diff --git a/src/web_ui/observability/cost_calculator.py b/src/web_ui/observability/cost_calculator.py new file mode 100644 index 00000000..4792b0cd --- /dev/null +++ b/src/web_ui/observability/cost_calculator.py @@ -0,0 +1,157 @@ +""" +LLM cost calculation based on token usage. +""" + +import logging + +logger = logging.getLogger(__name__) + +# Pricing as of January 2025 (USD per 1M tokens) +# Sources: OpenAI, Anthropic, Google, DeepSeek pricing pages +LLM_PRICING = { + # OpenAI Models + "gpt-4o": {"input": 2.50, "output": 10.00}, + "gpt-4o-mini": {"input": 0.15, "output": 0.60}, + "gpt-4-turbo": {"input": 10.00, "output": 30.00}, + "gpt-4": {"input": 30.00, "output": 60.00}, + "gpt-3.5-turbo": {"input": 0.50, "output": 1.50}, + # Anthropic Models + "claude-3.7-sonnet": {"input": 3.00, "output": 15.00}, + "claude-3-5-sonnet": {"input": 3.00, "output": 15.00}, + "claude-3-opus": {"input": 15.00, "output": 75.00}, + "claude-3-haiku": {"input": 0.25, "output": 1.25}, + "claude-3-sonnet": {"input": 3.00, "output": 15.00}, + # Google Models + "gemini-pro": {"input": 0.50, "output": 1.50}, + "gemini-1.5-pro": {"input": 1.25, "output": 5.00}, + "gemini-1.5-flash": {"input": 0.075, "output": 0.30}, + "gemini-2.0-flash": {"input": 0.10, "output": 0.40}, + # DeepSeek Models + "deepseek-v3": {"input": 0.14, "output": 0.28}, + "deepseek-chat": {"input": 0.14, "output": 0.28}, + # Mistral Models + "mistral-large": {"input": 2.00, "output": 6.00}, + "mistral-medium": {"input": 2.70, "output": 8.10}, + "mistral-small": {"input": 0.20, "output": 0.60}, + # Open Source / Self-hosted (free) + "ollama": {"input": 0.00, "output": 0.00}, + "llama": {"input": 0.00, "output": 0.00}, +} + + +def calculate_llm_cost(model: str, input_tokens: int, output_tokens: int) -> float: + """ + Calculate cost in USD for an LLM call. + + Args: + model: Model name/identifier + input_tokens: Number of input tokens + output_tokens: Number of output tokens + + Returns: + Cost in USD + """ + if not model or input_tokens == 0 or output_tokens == 0: + return 0.0 + + # Normalize model name (lowercase, remove version suffixes) + model_key = model.lower().strip() + + # Try exact match first + if model_key in LLM_PRICING: + pricing = LLM_PRICING[model_key] + else: + # Try fuzzy matching + pricing = None + for known_model in LLM_PRICING: + if known_model in model_key or model_key in known_model: + pricing = LLM_PRICING[known_model] + logger.debug(f"Matched '{model}' to pricing model '{known_model}'") + break + + if not pricing: + logger.warning(f"Unknown model for cost calculation: {model}") + return 0.0 + + # Calculate costs + input_cost = (input_tokens / 1_000_000) * pricing["input"] + output_cost = (output_tokens / 1_000_000) * pricing["output"] + + total_cost = input_cost + output_cost + + logger.debug(f"Cost for {model}: {input_tokens} in + {output_tokens} out = ${total_cost:.6f}") + + return total_cost + + +def estimate_task_cost( + model: str, estimated_steps: int, avg_tokens_per_step: int = 2000 +) -> dict[str, float]: + """ + Estimate the cost of a task. + + Args: + model: Model name + estimated_steps: Estimated number of steps + avg_tokens_per_step: Average tokens per step (input + output) + + Returns: + Dictionary with cost estimates + """ + # Assume 60% input, 40% output split + input_tokens = int(avg_tokens_per_step * 0.6) + output_tokens = int(avg_tokens_per_step * 0.4) + + cost_per_step = calculate_llm_cost(model, input_tokens, output_tokens) + total_cost = cost_per_step * estimated_steps + + return { + "cost_per_step": round(cost_per_step, 6), + "total_cost": round(total_cost, 4), + "total_tokens": avg_tokens_per_step * estimated_steps, + "estimated_steps": estimated_steps, + } + + +def get_pricing_info(model: str) -> dict[str, float] | None: + """ + Get pricing information for a model. + + Args: + model: Model name + + Returns: + Dictionary with input and output pricing per 1M tokens, or None if unknown + """ + model_key = model.lower().strip() + + # Try exact match + if model_key in LLM_PRICING: + return LLM_PRICING[model_key].copy() + + # Try fuzzy matching + for known_model in LLM_PRICING: + if known_model in model_key or model_key in known_model: + return LLM_PRICING[known_model].copy() + + return None + + +def format_cost(cost_usd: float) -> str: + """ + Format cost for display. + + Args: + cost_usd: Cost in USD + + Returns: + Formatted string + """ + if cost_usd == 0: + return "Free" + elif cost_usd < 0.01: + return f"${cost_usd:.6f}" + elif cost_usd < 1: + return f"${cost_usd:.4f}" + else: + return f"${cost_usd:.2f}" diff --git a/src/web_ui/observability/trace_models.py b/src/web_ui/observability/trace_models.py new file mode 100644 index 00000000..266fd533 --- /dev/null +++ b/src/web_ui/observability/trace_models.py @@ -0,0 +1,167 @@ +""" +Observability and tracing data structures for agent execution. +""" + +import time +from dataclasses import asdict, dataclass, field +from datetime import datetime +from enum import Enum +from typing import Any + + +class SpanType(str, Enum): + """Types of execution spans.""" + + AGENT_RUN = "agent_run" + LLM_CALL = "llm_call" + TOOL_CALL = "tool_call" + BROWSER_ACTION = "browser_action" + RETRIEVAL = "retrieval" + + +@dataclass +class TraceSpan: + """A single span in the execution trace.""" + + span_id: str + parent_id: str | None + span_type: SpanType + name: str + start_time: float + end_time: float | None = None + duration_ms: float | None = None + + # Inputs & Outputs + inputs: dict[str, Any] = field(default_factory=dict) + outputs: dict[str, Any] = field(default_factory=dict) + + # Metadata + metadata: dict[str, Any] = field(default_factory=dict) + tags: list[str] = field(default_factory=list) + + # LLM-specific + model_name: str | None = None + tokens_input: int | None = None + tokens_output: int | None = None + cost_usd: float | None = None + + # Status + status: str = "running" # running, completed, error + error: str | None = None + + def complete(self, outputs: dict[str, Any] | None = None): + """Mark span as completed.""" + self.end_time = time.time() + if self.start_time: + self.duration_ms = (self.end_time - self.start_time) * 1000 + self.status = "completed" + if outputs: + self.outputs = outputs + + def error_out(self, error: Exception): + """Mark span as error.""" + self.end_time = time.time() + if self.start_time: + self.duration_ms = (self.end_time - self.start_time) * 1000 + self.status = "error" + self.error = str(error) + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary for serialization.""" + return asdict(self) + + +@dataclass +class ExecutionTrace: + """Complete execution trace with all spans.""" + + trace_id: str + session_id: str + task: str + start_time: float + end_time: float | None = None + + spans: list[TraceSpan] = field(default_factory=list) + + # Aggregated metrics + total_tokens: int = 0 + total_cost_usd: float = 0.0 + llm_calls: int = 0 + actions_executed: int = 0 + + # Outcome + success: bool = False + final_output: Any = None + error: str | None = None + + def add_span(self, span: TraceSpan): + """Add a span to the trace.""" + self.spans.append(span) + + # Update aggregated metrics + if span.tokens_input: + self.total_tokens += span.tokens_input + if span.tokens_output: + self.total_tokens += span.tokens_output + if span.cost_usd: + self.total_cost_usd += span.cost_usd + if span.span_type == SpanType.LLM_CALL: + self.llm_calls += 1 + if span.span_type == SpanType.BROWSER_ACTION: + self.actions_executed += 1 + + def get_duration_ms(self) -> float: + """Get total trace duration.""" + if self.end_time: + return (self.end_time - self.start_time) * 1000 + return (time.time() - self.start_time) * 1000 + + def get_duration_seconds(self) -> float: + """Get total trace duration in seconds.""" + return self.get_duration_ms() / 1000 + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary for serialization.""" + return { + "trace_id": self.trace_id, + "session_id": self.session_id, + "task": self.task, + "start_time": self.start_time, + "end_time": self.end_time, + "duration_ms": self.get_duration_ms(), + "spans": [span.to_dict() for span in self.spans], + "total_tokens": self.total_tokens, + "total_cost_usd": self.total_cost_usd, + "llm_calls": self.llm_calls, + "actions_executed": self.actions_executed, + "success": self.success, + "final_output": str(self.final_output) if self.final_output else None, + "error": self.error, + } + + def get_summary(self) -> dict[str, Any]: + """Get a summary of the trace.""" + return { + "trace_id": self.trace_id, + "task": self.task, + "duration_seconds": round(self.get_duration_seconds(), 2), + "total_spans": len(self.spans), + "llm_calls": self.llm_calls, + "actions_executed": self.actions_executed, + "total_tokens": self.total_tokens, + "total_cost_usd": round(self.total_cost_usd, 4), + "success": self.success, + "timestamp": datetime.fromtimestamp(self.start_time).isoformat(), + } + + def get_llm_spans(self) -> list[TraceSpan]: + """Get all LLM call spans.""" + return [span for span in self.spans if span.span_type == SpanType.LLM_CALL] + + def get_action_spans(self) -> list[TraceSpan]: + """Get all browser action spans.""" + return [span for span in self.spans if span.span_type == SpanType.BROWSER_ACTION] + + def get_failed_spans(self) -> list[TraceSpan]: + """Get all failed spans.""" + return [span for span in self.spans if span.status == "error"] diff --git a/src/web_ui/observability/tracer.py b/src/web_ui/observability/tracer.py new file mode 100644 index 00000000..3f800cf9 --- /dev/null +++ b/src/web_ui/observability/tracer.py @@ -0,0 +1,102 @@ +""" +Agent tracer for execution observability. +""" + +import logging +import uuid +from collections.abc import AsyncGenerator +from contextlib import asynccontextmanager +from typing import Any + +from src.web_ui.observability.trace_models import ExecutionTrace, SpanType, TraceSpan + +logger = logging.getLogger(__name__) + + +class AgentTracer: + """Tracer for agent execution with span management.""" + + def __init__(self, session_id: str): + self.session_id = session_id + self.current_trace: ExecutionTrace | None = None + self.span_stack: list[TraceSpan] = [] # Stack for nested spans + + def start_trace(self, task: str) -> ExecutionTrace: + """Start a new trace.""" + import time + + trace_id = str(uuid.uuid4()) + self.current_trace = ExecutionTrace( + trace_id=trace_id, session_id=self.session_id, task=task, start_time=time.time() + ) + logger.info(f"Started trace {trace_id} for task: {task[:50]}") + return self.current_trace + + def end_trace(self, success: bool, final_output: Any = None, error: str | None = None): + """End the current trace.""" + import time + + if self.current_trace: + self.current_trace.end_time = time.time() + self.current_trace.success = success + self.current_trace.final_output = final_output + self.current_trace.error = error + + duration = self.current_trace.get_duration_seconds() + logger.info( + f"Ended trace {self.current_trace.trace_id} | " + f"Success: {success} | " + f"Duration: {duration:.2f}s | " + f"Cost: ${self.current_trace.total_cost_usd:.4f}" + ) + + @asynccontextmanager + async def span( + self, name: str, span_type: SpanType, inputs: dict[str, Any] | None = None, **metadata + ) -> AsyncGenerator[TraceSpan]: + """Context manager for creating spans.""" + import time + + # Create span + span_id = str(uuid.uuid4()) + parent_id = self.span_stack[-1].span_id if self.span_stack else None + + span = TraceSpan( + span_id=span_id, + parent_id=parent_id, + span_type=span_type, + name=name, + start_time=time.time(), + inputs=inputs or {}, + metadata=metadata, + ) + + # Push to stack + self.span_stack.append(span) + + # Add to trace + if self.current_trace: + self.current_trace.add_span(span) + + logger.debug(f"Started span: {name} ({span_type.value})") + + try: + yield span + span.complete() + logger.debug(f"Completed span: {name} in {span.duration_ms:.0f}ms") + except Exception as e: + span.error_out(e) + logger.error(f"Span {name} failed with error: {e}") + raise + finally: + # Pop from stack + if self.span_stack and self.span_stack[-1].span_id == span_id: + self.span_stack.pop() + + def get_current_trace(self) -> ExecutionTrace | None: + """Get the current trace.""" + return self.current_trace + + def get_current_span(self) -> TraceSpan | None: + """Get the current (top-level) span.""" + return self.span_stack[-1] if self.span_stack else None diff --git a/src/web_ui/plugins/plugin_interface.py b/src/web_ui/plugins/plugin_interface.py new file mode 100644 index 00000000..6d61b656 --- /dev/null +++ b/src/web_ui/plugins/plugin_interface.py @@ -0,0 +1,152 @@ +""" +Plugin system interface and base classes. +""" + +from abc import ABC, abstractmethod +from collections.abc import Callable +from dataclasses import dataclass, field +from typing import Any + + +@dataclass +class PluginManifest: + """Plugin metadata.""" + + id: str + name: str + version: str + author: str + description: str + dependencies: list[str] = field(default_factory=list) + permissions: list[str] = field(default_factory=list) + + # Entry points + controller_actions: list[str] = field(default_factory=list) # New browser actions + ui_components: list[str] = field(default_factory=list) # New UI tabs/components + event_handlers: dict[str, str] = field(default_factory=dict) # Event type -> handler method + + # Metadata + homepage: str | None = None + license: str | None = None + min_python_version: str = "3.11" + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary.""" + return { + "id": self.id, + "name": self.name, + "version": self.version, + "author": self.author, + "description": self.description, + "dependencies": self.dependencies, + "permissions": self.permissions, + "controller_actions": self.controller_actions, + "ui_components": self.ui_components, + "event_handlers": self.event_handlers, + "homepage": self.homepage, + "license": self.license, + "min_python_version": self.min_python_version, + } + + +class Plugin(ABC): + """ + Base class for all plugins. + + Plugins can extend functionality by: + 1. Adding new browser actions + 2. Adding UI components + 3. Listening to events + 4. Providing utilities + """ + + def __init__(self, manifest: PluginManifest): + self.manifest = manifest + self.enabled = True + self.config: dict[str, Any] = {} + + @abstractmethod + async def initialize(self): + """Initialize the plugin. Called when plugin is loaded.""" + pass + + @abstractmethod + async def shutdown(self): + """Clean up resources. Called when plugin is unloaded.""" + pass + + def get_controller_actions(self) -> dict[str, Callable]: + """ + Return custom browser actions this plugin provides. + + Returns: + Dict mapping action name to action function + """ + return {} + + def get_ui_components(self) -> dict[str, Callable]: + """ + Return UI components this plugin provides. + + Returns: + Dict mapping component name to Gradio component function + """ + return {} + + def get_event_handlers(self) -> dict[str, Callable]: + """ + Return event handlers this plugin provides. + + Returns: + Dict mapping event type to handler function + """ + return {} + + def get_config_schema(self) -> dict[str, Any]: + """ + Return JSON schema for plugin configuration. + + Used to generate configuration UI. + """ + return {} + + def configure(self, config: dict[str, Any]): + """ + Configure the plugin with user settings. + + Args: + config: Configuration dictionary + """ + self.config = config + + def get_info(self) -> dict[str, Any]: + """Get plugin information.""" + return { + "manifest": self.manifest.to_dict(), + "enabled": self.enabled, + "config": self.config, + } + + +class PluginError(Exception): + """Base exception for plugin-related errors.""" + + pass + + +class PluginLoadError(PluginError): + """Raised when a plugin fails to load.""" + + pass + + +class PluginInitError(PluginError): + """Raised when a plugin fails to initialize.""" + + pass + + +class PluginDependencyError(PluginError): + """Raised when plugin dependencies are not met.""" + + pass diff --git a/src/web_ui/utils/__init__.py b/src/web_ui/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/web_ui/utils/config.py b/src/web_ui/utils/config.py new file mode 100644 index 00000000..c8499c47 --- /dev/null +++ b/src/web_ui/utils/config.py @@ -0,0 +1,169 @@ +import os + +# Settings directory paths +SETTINGS_DIR = "./data" +DEFAULT_SETTINGS_FILE = "./data/default_settings.json" +SETTINGS_ARCHIVE_DIR = "./data/saved_configs" +OLD_SETTINGS_DIR = "./tmp/webui_settings" + +PROVIDER_DISPLAY_NAMES = { + "openai": "OpenAI", + "azure_openai": "Azure OpenAI", + "anthropic": "Anthropic", + "deepseek": "DeepSeek", + "google": "Google", + "alibaba": "Alibaba", + "moonshot": "MoonShot", + "unbound": "Unbound AI", + "ibm": "IBM", + "grok": "Grok", +} + +# Predefined model names for common providers +model_names = { + "anthropic": [ + "claude-3-5-sonnet-20241022", + "claude-3-5-sonnet-20240620", + "claude-3-opus-20240229", + ], + "openai": ["gpt-4o", "gpt-4", "gpt-3.5-turbo", "o3-mini"], + "deepseek": ["deepseek-chat", "deepseek-reasoner"], + "google": [ + "gemini-2.5-pro", + "gemini-2.5-flash", + "gemini-2.5-flash-lite", + "gemini-2.0-flash", + "gemini-2.0-flash-thinking-exp", + "gemini-1.5-flash-latest", + "gemini-1.5-flash-8b-latest", + "gemini-2.0-flash-thinking-exp-01-21", + "gemini-2.0-pro-exp-02-05", + "gemini-2.5-pro-preview-03-25", + "gemini-2.5-flash-preview-04-17", + ], + "ollama": [ + "qwen2.5:7b", + "qwen2.5:14b", + "qwen2.5:32b", + "qwen2.5-coder:14b", + "qwen2.5-coder:32b", + "llama2:7b", + "deepseek-r1:14b", + "deepseek-r1:32b", + ], + "azure_openai": ["gpt-4o", "gpt-4", "gpt-3.5-turbo"], + "mistral": [ + "pixtral-large-latest", + "mistral-large-latest", + "mistral-small-latest", + "ministral-8b-latest", + ], + "alibaba": ["qwen-plus", "qwen-max", "qwen-vl-max", "qwen-vl-plus", "qwen-turbo", "qwen-long"], + "moonshot": ["moonshot-v1-32k-vision-preview", "moonshot-v1-8k-vision-preview"], + "unbound": ["gemini-2.0-flash", "gpt-4o-mini", "gpt-4o", "gpt-4.5-preview"], + "grok": [ + "grok-3", + "grok-3-fast", + "grok-3-mini", + "grok-3-mini-fast", + "grok-2-vision", + "grok-2-image", + "grok-2", + ], + "siliconflow": [ + "deepseek-ai/DeepSeek-R1", + "deepseek-ai/DeepSeek-V3", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B", + "deepseek-ai/DeepSeek-V2.5", + "deepseek-ai/deepseek-vl2", + "Qwen/Qwen2.5-72B-Instruct-128K", + "Qwen/Qwen2.5-72B-Instruct", + "Qwen/Qwen2.5-32B-Instruct", + "Qwen/Qwen2.5-14B-Instruct", + "Qwen/Qwen2.5-7B-Instruct", + "Qwen/Qwen2.5-Coder-32B-Instruct", + "Qwen/Qwen2.5-Coder-7B-Instruct", + "Qwen/Qwen2-7B-Instruct", + "Qwen/Qwen2-1.5B-Instruct", + "Qwen/QwQ-32B-Preview", + "Qwen/Qwen2-VL-72B-Instruct", + "Qwen/Qwen2.5-VL-32B-Instruct", + "Qwen/Qwen2.5-VL-72B-Instruct", + "TeleAI/TeleChat2", + "THUDM/glm-4-9b-chat", + "Vendor-A/Qwen/Qwen2.5-72B-Instruct", + "internlm/internlm2_5-7b-chat", + "internlm/internlm2_5-20b-chat", + "Pro/Qwen/Qwen2.5-7B-Instruct", + "Pro/Qwen/Qwen2-7B-Instruct", + "Pro/Qwen/Qwen2-1.5B-Instruct", + "Pro/THUDM/chatglm3-6b", + "Pro/THUDM/glm-4-9b-chat", + ], + "ibm": [ + "ibm/granite-vision-3.1-2b-preview", + "meta-llama/llama-4-maverick-17b-128e-instruct-fp8", + "meta-llama/llama-3-2-90b-vision-instruct", + ], + "modelscope": [ + "Qwen/Qwen2.5-Coder-32B-Instruct", + "Qwen/Qwen2.5-Coder-14B-Instruct", + "Qwen/Qwen2.5-Coder-7B-Instruct", + "Qwen/Qwen2.5-72B-Instruct", + "Qwen/Qwen2.5-32B-Instruct", + "Qwen/Qwen2.5-14B-Instruct", + "Qwen/Qwen2.5-7B-Instruct", + "Qwen/QwQ-32B-Preview", + "Qwen/Qwen2.5-VL-3B-Instruct", + "Qwen/Qwen2.5-VL-7B-Instruct", + "Qwen/Qwen2.5-VL-32B-Instruct", + "Qwen/Qwen2.5-VL-72B-Instruct", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-14B", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B", + "deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B", + "deepseek-ai/DeepSeek-R1", + "deepseek-ai/DeepSeek-V3", + "Qwen/Qwen3-1.7B", + "Qwen/Qwen3-4B", + "Qwen/Qwen3-8B", + "Qwen/Qwen3-14B", + "Qwen/Qwen3-30B-A3B", + "Qwen/Qwen3-32B", + "Qwen/Qwen3-235B-A22B", + ], +} + + +def ensure_settings_directories(): + """Ensure settings directories exist.""" + os.makedirs(SETTINGS_DIR, exist_ok=True) + os.makedirs(SETTINGS_ARCHIVE_DIR, exist_ok=True) + + +def is_runtime_component(comp_id: str) -> bool: + """ + Check if a component ID represents runtime-only data that shouldn't be saved. + + Args: + comp_id: Component ID to check + + Returns: + True if component is runtime-only and should be excluded from saves + """ + runtime_patterns = [ + "chat_history", + "current_task", + "agent_task_id", + "task_id", + "save_dir", + "response_event", + "user_help_response", + "chatbot", + "visible", + "file", # File uploads + ] + return any(pattern in comp_id.lower() for pattern in runtime_patterns) diff --git a/src/web_ui/utils/llm_provider.py b/src/web_ui/utils/llm_provider.py new file mode 100644 index 00000000..d5fb91b8 --- /dev/null +++ b/src/web_ui/utils/llm_provider.py @@ -0,0 +1,326 @@ +import os +from typing import ( + Any, +) + +from langchain_anthropic import ChatAnthropic +from langchain_core.language_models.base import ( + LanguageModelInput, +) +from langchain_core.messages import ( + AIMessage, + SystemMessage, +) +from langchain_core.runnables import RunnableConfig +from langchain_google_genai import ChatGoogleGenerativeAI +from langchain_ibm import ChatWatsonx +from langchain_mistralai import ChatMistralAI +from langchain_ollama import ChatOllama +from langchain_openai import AzureChatOpenAI, ChatOpenAI +from openai import OpenAI +from pydantic import SecretStr + +from src.web_ui.utils import config + + +class DeepSeekR1ChatOpenAI(ChatOpenAI): + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.client = OpenAI( + base_url=kwargs.get("openai_api_base"), api_key=kwargs.get("openai_api_key") + ) + + async def ainvoke( + self, + input: LanguageModelInput, + config: RunnableConfig | None = None, + *, + stop: list[str] | None = None, + **kwargs: Any, + ) -> AIMessage: + message_history = [] + for input_ in input: + if isinstance(input_, SystemMessage): + message_history.append({"role": "system", "content": input_.content}) + elif isinstance(input_, AIMessage): + message_history.append({"role": "assistant", "content": input_.content}) + else: + message_history.append({"role": "user", "content": input_.content}) + + response = self.client.chat.completions.create( + model=self.model_name, messages=message_history + ) + + reasoning_content = response.choices[0].message.reasoning_content + content = response.choices[0].message.content + return AIMessage(content=content, reasoning_content=reasoning_content) + + def invoke( + self, + input: LanguageModelInput, + config: RunnableConfig | None = None, + *, + stop: list[str] | None = None, + **kwargs: Any, + ) -> AIMessage: + message_history = [] + for input_ in input: + if isinstance(input_, SystemMessage): + message_history.append({"role": "system", "content": input_.content}) + elif isinstance(input_, AIMessage): + message_history.append({"role": "assistant", "content": input_.content}) + else: + message_history.append({"role": "user", "content": input_.content}) + + response = self.client.chat.completions.create( + model=self.model_name, messages=message_history + ) + + reasoning_content = response.choices[0].message.reasoning_content + content = response.choices[0].message.content + return AIMessage(content=content, reasoning_content=reasoning_content) + + +class DeepSeekR1ChatOllama(ChatOllama): + async def ainvoke( + self, + input: LanguageModelInput, + config: RunnableConfig | None = None, + *, + stop: list[str] | None = None, + **kwargs: Any, + ) -> AIMessage: + org_ai_message = await super().ainvoke(input=input) + org_content = org_ai_message.content + reasoning_content = org_content.split("")[0].replace("", "") + content = org_content.split("")[1] + if "**JSON Response:**" in content: + content = content.split("**JSON Response:**")[-1] + return AIMessage(content=content, reasoning_content=reasoning_content) + + def invoke( + self, + input: LanguageModelInput, + config: RunnableConfig | None = None, + *, + stop: list[str] | None = None, + **kwargs: Any, + ) -> AIMessage: + org_ai_message = super().invoke(input=input) + org_content = org_ai_message.content + reasoning_content = org_content.split("")[0].replace("", "") + content = org_content.split("")[1] + if "**JSON Response:**" in content: + content = content.split("**JSON Response:**")[-1] + return AIMessage(content=content, reasoning_content=reasoning_content) + + +def get_llm_model(provider: str, **kwargs): + """ + Get LLM model + :param provider: LLM provider + :param kwargs: + :return: + """ + if provider not in ["ollama", "bedrock"]: + env_var = f"{provider.upper()}_API_KEY" + api_key = kwargs.get("api_key", "") or os.getenv(env_var, "") + if not api_key: + provider_display = config.PROVIDER_DISPLAY_NAMES.get(provider, provider.upper()) + error_msg = f"💥 {provider_display} API key not found! 🔑 Please set the `{env_var}` environment variable or provide it in the UI." + raise ValueError(error_msg) + kwargs["api_key"] = api_key + + if provider == "anthropic": + if not kwargs.get("base_url", ""): + base_url = "https://api.anthropic.com" + else: + base_url = kwargs.get("base_url") + + return ChatAnthropic( + model=kwargs.get("model_name", "claude-3-5-sonnet-20241022"), + temperature=kwargs.get("temperature", 0.0), + anthropic_api_url=base_url, + anthropic_api_key=SecretStr(api_key) if api_key else SecretStr(""), + ) + elif provider == "mistral": + if not kwargs.get("base_url", ""): + base_url = os.getenv("MISTRAL_ENDPOINT", "https://api.mistral.ai/v1") + else: + base_url = kwargs.get("base_url") + if not kwargs.get("api_key", ""): + api_key = os.getenv("MISTRAL_API_KEY", "") + else: + api_key = kwargs.get("api_key") + + return ChatMistralAI( + model=kwargs.get("model_name", "mistral-large-latest"), + temperature=kwargs.get("temperature", 0.0), + endpoint=base_url, + mistral_api_key=SecretStr(api_key) if api_key else None, + ) + elif provider == "openai": + if not kwargs.get("base_url", ""): + base_url = os.getenv("OPENAI_ENDPOINT", "https://api.openai.com/v1") + else: + base_url = kwargs.get("base_url") + + return ChatOpenAI( + model_name=kwargs.get("model_name", "gpt-4o"), + temperature=kwargs.get("temperature", 0.0), + openai_api_base=base_url, + openai_api_key=SecretStr(api_key) if api_key else None, + ) + elif provider == "grok": + if not kwargs.get("base_url", ""): + base_url = os.getenv("GROK_ENDPOINT", "https://api.x.ai/v1") + else: + base_url = kwargs.get("base_url") + + return ChatOpenAI( + model_name=kwargs.get("model_name", "grok-3"), + temperature=kwargs.get("temperature", 0.0), + openai_api_base=base_url, + openai_api_key=SecretStr(api_key) if api_key else None, + ) + elif provider == "deepseek": + if not kwargs.get("base_url", ""): + base_url = os.getenv("DEEPSEEK_ENDPOINT", "") + else: + base_url = kwargs.get("base_url") + + if kwargs.get("model_name", "deepseek-chat") == "deepseek-reasoner": + return DeepSeekR1ChatOpenAI( + model_name=kwargs.get("model_name", "deepseek-reasoner"), + temperature=kwargs.get("temperature", 0.0), + openai_api_base=base_url, + openai_api_key=api_key, + ) + else: + return ChatOpenAI( + model_name=kwargs.get("model_name", "deepseek-chat"), + temperature=kwargs.get("temperature", 0.0), + openai_api_base=base_url, + openai_api_key=SecretStr(api_key) if api_key else None, + ) + elif provider == "google": + return ChatGoogleGenerativeAI( + model=kwargs.get("model_name", "gemini-2.0-flash-exp"), + temperature=kwargs.get("temperature", 0.0), + google_api_key=SecretStr(api_key) if api_key else None, + ) + elif provider == "ollama": + if not kwargs.get("base_url", ""): + base_url = os.getenv("OLLAMA_ENDPOINT", "http://localhost:11434") + else: + base_url = kwargs.get("base_url") + + if "deepseek-r1" in kwargs.get("model_name", "qwen2.5:7b"): + return DeepSeekR1ChatOllama( + model=kwargs.get("model_name", "deepseek-r1:14b"), + temperature=kwargs.get("temperature", 0.0), + num_ctx=kwargs.get("num_ctx", 32000), + base_url=base_url, + ) + else: + return ChatOllama( + model=kwargs.get("model_name", "qwen2.5:7b"), + temperature=kwargs.get("temperature", 0.0), + num_ctx=kwargs.get("num_ctx", 32000), + num_predict=kwargs.get("num_predict", 1024), + base_url=base_url, + ) + elif provider == "azure_openai": + if not kwargs.get("base_url", ""): + base_url = os.getenv("AZURE_OPENAI_ENDPOINT", "") + else: + base_url = kwargs.get("base_url") + api_version = kwargs.get("api_version", "") or os.getenv( + "AZURE_OPENAI_API_VERSION", "2025-01-01-preview" + ) + # AzureChatOpenAI uses deployment_name instead of model + return AzureChatOpenAI( + deployment_name=kwargs.get("model_name", "gpt-4o"), + temperature=kwargs.get("temperature", 0.0), + openai_api_version=api_version, + azure_endpoint=base_url, + openai_api_key=SecretStr(api_key) if api_key else SecretStr(""), + ) + elif provider == "alibaba": + if not kwargs.get("base_url", ""): + base_url = os.getenv( + "ALIBABA_ENDPOINT", "https://dashscope.aliyuncs.com/compatible-mode/v1" + ) + else: + base_url = kwargs.get("base_url") + + return ChatOpenAI( + model_name=kwargs.get("model_name", "qwen-plus"), + temperature=kwargs.get("temperature", 0.0), + openai_api_base=base_url, + openai_api_key=SecretStr(api_key) if api_key else None, + ) + elif provider == "ibm": + parameters = { + "temperature": kwargs.get("temperature", 0.0), + "max_tokens": kwargs.get("num_ctx", 32000), + } + if not kwargs.get("base_url", ""): + base_url = os.getenv("IBM_ENDPOINT", "https://us-south.ml.cloud.ibm.com") + else: + base_url = kwargs.get("base_url") + + return ChatWatsonx( + model_id=kwargs.get("model_name", "ibm/granite-vision-3.1-2b-preview"), + url=SecretStr(base_url) if base_url else SecretStr(""), + project_id=os.getenv("IBM_PROJECT_ID"), + apikey=SecretStr(os.getenv("IBM_API_KEY") or ""), + params=parameters, + ) + elif provider == "moonshot": + return ChatOpenAI( + model_name=kwargs.get("model_name", "moonshot-v1-32k-vision-preview"), + temperature=kwargs.get("temperature", 0.0), + openai_api_base=os.getenv("MOONSHOT_ENDPOINT"), + openai_api_key=SecretStr(os.getenv("MOONSHOT_API_KEY") or ""), + ) + elif provider == "unbound": + return ChatOpenAI( + model_name=kwargs.get("model_name", "gpt-4o-mini"), + temperature=kwargs.get("temperature", 0.0), + openai_api_base=os.getenv("UNBOUND_ENDPOINT", "https://api.getunbound.ai"), + openai_api_key=SecretStr(api_key) if api_key else None, + ) + elif provider == "siliconflow": + if not kwargs.get("api_key", ""): + api_key = os.getenv("SiliconFLOW_API_KEY", "") + else: + api_key = kwargs.get("api_key") + if not kwargs.get("base_url", ""): + base_url = os.getenv("SiliconFLOW_ENDPOINT", "") + else: + base_url = kwargs.get("base_url") + return ChatOpenAI( + openai_api_key=SecretStr(api_key) if api_key else None, + openai_api_base=base_url, + model_name=kwargs.get("model_name", "Qwen/QwQ-32B"), + temperature=kwargs.get("temperature", 0.0), + ) + elif provider == "modelscope": + if not kwargs.get("api_key", ""): + api_key = os.getenv("MODELSCOPE_API_KEY", "") + else: + api_key = kwargs.get("api_key") + if not kwargs.get("base_url", ""): + base_url = os.getenv("MODELSCOPE_ENDPOINT", "") + else: + base_url = kwargs.get("base_url") + return ChatOpenAI( + openai_api_key=SecretStr(api_key) if api_key else None, + openai_api_base=base_url, + model_name=kwargs.get("model_name", "Qwen/QwQ-32B"), + temperature=kwargs.get("temperature", 0.0), + extra_body={"enable_thinking": False}, + ) + else: + raise ValueError(f"Unsupported provider: {provider}") diff --git a/src/web_ui/utils/mcp_client.py b/src/web_ui/utils/mcp_client.py new file mode 100644 index 00000000..e97cc2a9 --- /dev/null +++ b/src/web_ui/utils/mcp_client.py @@ -0,0 +1,257 @@ +import inspect +import logging +import uuid +from datetime import date, datetime, time +from enum import Enum +from typing import Any, Union, get_type_hints + +from browser_use.controller.registry.views import ActionModel +from langchain.tools import BaseTool +from langchain_mcp_adapters.client import MultiServerMCPClient +from pydantic import BaseModel, Field, create_model + +logger = logging.getLogger(__name__) + + +async def setup_mcp_client_and_tools( + mcp_server_config: dict[str, Any], +) -> MultiServerMCPClient | None: + """ + Initializes the MultiServerMCPClient and returns it. + + As of langchain-mcp-adapters 0.1.0, the client is no longer used as a context manager. + Instead, use client.get_tools() directly. + + Returns: + MultiServerMCPClient | None: The initialized client instance, or None on failure. + """ + + logger.info("Initializing MultiServerMCPClient...") + + if not mcp_server_config: + logger.error("No MCP server configuration provided.") + return None + + try: + if "mcpServers" in mcp_server_config: + mcp_server_config = mcp_server_config["mcpServers"] + + # As of langchain-mcp-adapters 0.1.0, no longer use as context manager + client = MultiServerMCPClient(mcp_server_config) + logger.info("MCP client initialized successfully (using new API)") + return client + + except Exception as e: + logger.error(f"Failed to setup MCP client: {e}", exc_info=True) + return None + + +def create_tool_param_model(tool: BaseTool) -> type[BaseModel]: + """Creates a Pydantic model from a LangChain tool's schema""" + + # Get tool schema information + json_schema = tool.args_schema + tool_name = tool.name + + # If the tool already has a schema defined, convert it to a new param_model + if json_schema is not None: + # Create new parameter model + params = {} + + # Process properties if they exist + if "properties" in json_schema: + # Find required fields + required_fields: set[str] = set(json_schema.get("required", [])) + + for prop_name, prop_details in json_schema["properties"].items(): + field_type = resolve_type(prop_details, f"{tool_name}_{prop_name}") + + # Check if parameter is required + is_required = prop_name in required_fields + + # Get default value and description + default_value = prop_details.get("default", ... if is_required else None) + description = prop_details.get("description", "") + + # Add field constraints + field_kwargs = {"default": default_value} + if description: + field_kwargs["description"] = description + + # Add additional constraints if present + if "minimum" in prop_details: + field_kwargs["ge"] = prop_details["minimum"] + if "maximum" in prop_details: + field_kwargs["le"] = prop_details["maximum"] + if "minLength" in prop_details: + field_kwargs["min_length"] = prop_details["minLength"] + if "maxLength" in prop_details: + field_kwargs["max_length"] = prop_details["maxLength"] + if "pattern" in prop_details: + field_kwargs["pattern"] = prop_details["pattern"] + + # Add to parameters dictionary + params[prop_name] = (field_type, Field(**field_kwargs)) + + return create_model( + f"{tool_name}_parameters", + __base__=ActionModel, + **params, # type: ignore + ) + + # If no schema is defined, extract parameters from the _run method + run_method = tool._run + sig = inspect.signature(run_method) + + # Get type hints for better type information + try: + type_hints = get_type_hints(run_method) + except Exception: + type_hints = {} + + params = {} + for name, param in sig.parameters.items(): + # Skip 'self' parameter and any other parameters you want to exclude + if name == "self": + continue + + # Get annotation from type hints if available, otherwise from signature + annotation = type_hints.get(name, param.annotation) + if annotation == inspect.Parameter.empty: + annotation = Any + + # Use default value if available, otherwise make it required + if param.default != param.empty: + params[name] = (annotation, param.default) + else: + params[name] = (annotation, ...) + + return create_model( + f"{tool_name}_parameters", + __base__=ActionModel, + **params, # type: ignore + ) + + +def resolve_type(prop_details: dict[str, Any], prefix: str = "") -> Any: + """Recursively resolves JSON schema type to Python/Pydantic type""" + + # Handle reference types + if "$ref" in prop_details: + # In a real application, reference resolution would be needed + return Any + + # Basic type mapping + type_mapping = { + "string": str, + "integer": int, + "number": float, + "boolean": bool, + "array": list, + "object": dict, + "null": type(None), + } + + # Handle formatted strings + if prop_details.get("type") == "string" and "format" in prop_details: + format_mapping = { + "date-time": datetime, + "date": date, + "time": time, + "email": str, + "uri": str, + "url": str, + "uuid": uuid.UUID, + "binary": bytes, + } + return format_mapping.get(prop_details["format"], str) + + # Handle enum types + if "enum" in prop_details: + enum_values = prop_details["enum"] + # Create dynamic enum class with safe names + enum_dict = {} + for i, v in enumerate(enum_values): + # Ensure enum names are valid Python identifiers + if isinstance(v, str): + key = v.upper().replace(" ", "_").replace("-", "_") + if not key.isidentifier(): + key = f"VALUE_{i}" + else: + key = f"VALUE_{i}" + enum_dict[key] = v + + # Only create enum if we have values + if enum_dict: + return Enum(f"{prefix}_Enum", enum_dict) + return str # Fallback + + # Handle array types + if prop_details.get("type") == "array" and "items" in prop_details: + item_type = resolve_type(prop_details["items"], f"{prefix}_item") + return list[item_type] # type: ignore + + # Handle object types with properties + if prop_details.get("type") == "object" and "properties" in prop_details: + nested_params = {} + for nested_name, nested_details in prop_details["properties"].items(): + nested_type = resolve_type(nested_details, f"{prefix}_{nested_name}") + # Get required field info + required_fields = prop_details.get("required", []) + is_required = nested_name in required_fields + default_value = nested_details.get("default", ... if is_required else None) + description = nested_details.get("description", "") + + field_kwargs = {"default": default_value} + if description: + field_kwargs["description"] = description + + nested_params[nested_name] = (nested_type, Field(**field_kwargs)) + + # Create nested model + nested_model = create_model(f"{prefix}_Model", **nested_params) + return nested_model + + # Handle union types (oneOf, anyOf) + if "oneOf" in prop_details or "anyOf" in prop_details: + union_schema = prop_details.get("oneOf") or prop_details.get("anyOf") + if union_schema: + union_types = [] + for i, t in enumerate(union_schema): + union_types.append(resolve_type(t, f"{prefix}_{i}")) + + if union_types: + return Union.__getitem__(tuple(union_types)) # type: ignore + return Any + + # Handle allOf (intersection types) + if "allOf" in prop_details: + nested_params = {} + for i, schema_part in enumerate(prop_details["allOf"]): + if "properties" in schema_part: + for nested_name, nested_details in schema_part["properties"].items(): + nested_type = resolve_type(nested_details, f"{prefix}_allOf_{i}_{nested_name}") + # Check if required + required_fields = schema_part.get("required", []) + is_required = nested_name in required_fields + nested_params[nested_name] = (nested_type, ... if is_required else None) + + # Create composite model + if nested_params: + composite_model = create_model(f"{prefix}_CompositeModel", **nested_params) + return composite_model + return dict + + # Default to basic types + schema_type = prop_details.get("type", "string") + if isinstance(schema_type, list): + # Handle multiple types (e.g., ["string", "null"]) + non_null_types = [t for t in schema_type if t != "null"] + if non_null_types: + primary_type = type_mapping.get(non_null_types[0], Any) + if "null" in schema_type: + return primary_type | None + return primary_type + return Any + + return type_mapping.get(schema_type, Any) diff --git a/src/web_ui/utils/mcp_config.py b/src/web_ui/utils/mcp_config.py new file mode 100644 index 00000000..70400689 --- /dev/null +++ b/src/web_ui/utils/mcp_config.py @@ -0,0 +1,242 @@ +""" +MCP Configuration Manager + +Handles loading, saving, and validating MCP (Model Context Protocol) server configurations. +""" + +import json +import logging +import os +from pathlib import Path +from typing import Any + +logger = logging.getLogger(__name__) + +# Default MCP configuration file location +DEFAULT_MCP_CONFIG_PATH = Path("./data/mcp.json") + + +def get_mcp_config_path() -> Path: + """ + Get the MCP configuration file path. + + Priority: + 1. MCP_CONFIG_PATH environment variable + 2. ./data/mcp.json in data directory + + Returns: + Path to the MCP configuration file + """ + custom_path = os.getenv("MCP_CONFIG_PATH") + if custom_path: + return Path(custom_path) + return DEFAULT_MCP_CONFIG_PATH + + +def validate_mcp_config(config: dict[str, Any]) -> tuple[bool, str | None]: + """ + Validate MCP configuration structure. + + Args: + config: MCP configuration dictionary + + Returns: + Tuple of (is_valid, error_message) + """ + if not isinstance(config, dict): + return False, "Configuration must be a dictionary" + + # Check if config has mcpServers key or is already in the correct format + if "mcpServers" in config: + servers = config["mcpServers"] + else: + servers = config + + if not isinstance(servers, dict): + return False, "MCP servers configuration must be a dictionary" + + # Validate each server configuration + for server_name, server_config in servers.items(): + if not isinstance(server_config, dict): + return False, f"Server '{server_name}' configuration must be a dictionary" + + # Check for required fields + if "command" not in server_config: + return False, f"Server '{server_name}' must have a 'command' field" + + # Validate command is a string + if not isinstance(server_config["command"], str): + return False, f"Server '{server_name}' command must be a string" + + # Validate args if present + if "args" in server_config: + if not isinstance(server_config["args"], list): + return False, f"Server '{server_name}' args must be a list" + + # All args should be strings + for i, arg in enumerate(server_config["args"]): + if not isinstance(arg, str): + return False, f"Server '{server_name}' args[{i}] must be a string" + + # Validate env if present + if "env" in server_config: + if not isinstance(server_config["env"], dict): + return False, f"Server '{server_name}' env must be a dictionary" + + # All env values should be strings + for key, value in server_config["env"].items(): + if not isinstance(value, str): + return False, f"Server '{server_name}' env['{key}'] must be a string" + + return True, None + + +def load_mcp_config(config_path: Path | None = None) -> dict[str, Any] | None: + """ + Load MCP configuration from file. + + Args: + config_path: Optional path to configuration file. If None, uses default path. + + Returns: + MCP configuration dictionary or None if file doesn't exist or is invalid + """ + if config_path is None: + config_path = get_mcp_config_path() + + if not config_path.exists(): + logger.info(f"MCP configuration file not found at {config_path}") + return None + + try: + with open(config_path, encoding="utf-8") as f: + config = json.load(f) + + # Validate configuration + is_valid, error_msg = validate_mcp_config(config) + if not is_valid: + logger.error(f"Invalid MCP configuration: {error_msg}") + return None + + logger.info(f"Successfully loaded MCP configuration from {config_path}") + return config + + except json.JSONDecodeError as e: + logger.error(f"Failed to parse MCP configuration JSON: {e}") + return None + except Exception as e: + logger.error(f"Failed to load MCP configuration: {e}", exc_info=True) + return None + + +def save_mcp_config(config: dict[str, Any], config_path: Path | None = None) -> bool: + """ + Save MCP configuration to file. + + Args: + config: MCP configuration dictionary + config_path: Optional path to configuration file. If None, uses default path. + + Returns: + True if saved successfully, False otherwise + """ + if config_path is None: + config_path = get_mcp_config_path() + + # Validate before saving + is_valid, error_msg = validate_mcp_config(config) + if not is_valid: + logger.error(f"Cannot save invalid MCP configuration: {error_msg}") + return False + + try: + # Create parent directories if they don't exist + config_path.parent.mkdir(parents=True, exist_ok=True) + + # Save with pretty printing + with open(config_path, "w", encoding="utf-8") as f: + json.dump(config, f, indent=2, ensure_ascii=False) + + logger.info(f"Successfully saved MCP configuration to {config_path}") + return True + + except Exception as e: + logger.error(f"Failed to save MCP configuration: {e}", exc_info=True) + return False + + +def get_default_mcp_config() -> dict[str, Any]: + """ + Get default/empty MCP configuration structure. + + Returns: + Default MCP configuration dictionary + """ + return {"mcpServers": {}} + + +def merge_mcp_configs( + base_config: dict[str, Any], override_config: dict[str, Any] +) -> dict[str, Any]: + """ + Merge two MCP configurations, with override_config taking precedence. + + Args: + base_config: Base configuration + override_config: Configuration to override base with + + Returns: + Merged configuration + """ + # Extract server configs + base_servers = base_config.get( + "mcpServers", base_config if isinstance(base_config, dict) else {} + ) + override_servers = override_config.get( + "mcpServers", override_config if isinstance(override_config, dict) else {} + ) + + # Merge servers + merged_servers = {**base_servers, **override_servers} + + return {"mcpServers": merged_servers} + + +def get_mcp_server_names(config: dict[str, Any]) -> list[str]: + """ + Get list of MCP server names from configuration. + + Args: + config: MCP configuration dictionary + + Returns: + List of server names + """ + if "mcpServers" in config: + return list(config["mcpServers"].keys()) + return list(config.keys()) + + +def get_mcp_config_summary(config: dict[str, Any]) -> str: + """ + Get a human-readable summary of MCP configuration. + + Args: + config: MCP configuration dictionary + + Returns: + Summary string + """ + server_names = get_mcp_server_names(config) + + if not server_names: + return "No MCP servers configured" + + summary = f"MCP Servers ({len(server_names)}):\n" + for name in server_names: + servers = config.get("mcpServers", config) + server_config = servers[name] + command = server_config.get("command", "unknown") + summary += f" - {name}: {command}\n" + + return summary.strip() diff --git a/src/web_ui/utils/utils.py b/src/web_ui/utils/utils.py new file mode 100644 index 00000000..697beb85 --- /dev/null +++ b/src/web_ui/utils/utils.py @@ -0,0 +1,36 @@ +import base64 +import os +import time +from pathlib import Path + + +def encode_image(img_path): + if not img_path: + return None + with open(img_path, "rb") as fin: + image_data = base64.b64encode(fin.read()).decode("utf-8") + return image_data + + +def get_latest_files(directory: str, file_types: list | None = None) -> dict[str, str | None]: + """Get the latest recording and trace files""" + if file_types is None: + file_types = [".webm", ".zip"] + latest_files: dict[str, str | None] = dict.fromkeys(file_types) + + if not os.path.exists(directory): + os.makedirs(directory, exist_ok=True) + return latest_files + + for file_type in file_types: + try: + matches = list(Path(directory).rglob(f"*{file_type}")) + if matches: + latest = max(matches, key=lambda p: p.stat().st_mtime) + # Only return files that are complete (not being written) + if time.time() - latest.stat().st_mtime > 1.0: + latest_files[file_type] = str(latest) + except Exception as e: + print(f"Error getting latest {file_type} file: {e}") + + return latest_files diff --git a/src/web_ui/utils/workflow_graph.py b/src/web_ui/utils/workflow_graph.py new file mode 100644 index 00000000..13859d91 --- /dev/null +++ b/src/web_ui/utils/workflow_graph.py @@ -0,0 +1,407 @@ +""" +Workflow graph builder for visualizing agent execution. +""" + +import time +from dataclasses import dataclass +from enum import Enum +from typing import Any + + +class NodeType(str, Enum): + """Types of workflow nodes.""" + + START = "start" + THINKING = "thinking" + ACTION = "action" + RESULT = "result" + ERROR = "error" + END = "end" + + +class NodeStatus(str, Enum): + """Status of a workflow node.""" + + PENDING = "pending" + RUNNING = "running" + COMPLETED = "completed" + ERROR = "error" + SKIPPED = "skipped" + + +@dataclass +class WorkflowNode: + """A single node in the workflow graph.""" + + id: str + type: NodeType + position: dict[str, float] + data: dict[str, Any] + status: NodeStatus = NodeStatus.PENDING + start_time: float | None = None + end_time: float | None = None + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary for JSON serialization.""" + result = { + "id": self.id, + "type": self.type.value, + "position": self.position, + "data": {**self.data, "status": self.status.value}, + } + + # Add timing information + if self.start_time and self.end_time: + result["data"]["duration"] = round( + (self.end_time - self.start_time) * 1000, 2 + ) # milliseconds + + return result + + +@dataclass +class WorkflowEdge: + """A connection between workflow nodes.""" + + id: str + source: str + target: str + animated: bool = False + label: str | None = None + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary for JSON serialization.""" + result = { + "id": self.id, + "source": self.source, + "target": self.target, + } + + if self.animated: + result["animated"] = True + if self.label: + result["label"] = self.label + + return result + + +class WorkflowGraphBuilder: + """Builds workflow graph data from agent execution.""" + + def __init__(self): + self.nodes: list[WorkflowNode] = [] + self.edges: list[WorkflowEdge] = [] + self.node_counter = 0 + self.current_depth = 0 + self.horizontal_offset = 250 + self.vertical_spacing = 120 + + def add_start_node(self, task: str) -> str: + """Add the starting node.""" + node_id = self._generate_node_id() + + node = WorkflowNode( + id=node_id, + type=NodeType.START, + position={"x": self.horizontal_offset, "y": 0}, + data={"label": "Start", "task": task, "icon": "🚀"}, + status=NodeStatus.COMPLETED, + start_time=time.time(), + end_time=time.time(), + ) + + self.nodes.append(node) + self.current_depth = 1 + return node_id + + def add_thinking_node(self, parent_id: str, content: str, model_name: str | None = None) -> str: + """Add a thinking/reasoning node.""" + node_id = self._generate_node_id() + + # Calculate position based on parent + parent_node = self._get_node_by_id(parent_id) + y_pos = ( + parent_node.position["y"] + self.vertical_spacing + if parent_node + else self.vertical_spacing + ) + + node = WorkflowNode( + id=node_id, + type=NodeType.THINKING, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": "Thinking", + "content": content[:200] + "..." if len(content) > 200 else content, + "full_content": content, + "model": model_name, + "icon": "🤔", + }, + status=NodeStatus.RUNNING, + start_time=time.time(), + ) + + self.nodes.append(node) + + # Add edge from parent + edge = WorkflowEdge( + id=f"edge_{parent_id}_{node_id}", source=parent_id, target=node_id, animated=True + ) + self.edges.append(edge) + + self.current_depth += 1 + return node_id + + def add_action_node( + self, + parent_id: str, + action: str, + params: dict[str, Any], + status: NodeStatus = NodeStatus.PENDING, + ) -> str: + """Add an action node.""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = ( + parent_node.position["y"] + self.vertical_spacing + if parent_node + else self.vertical_spacing + ) + + # Format action label + action_label = self._format_action_label(action) + + # Get appropriate icon + icon = self._get_action_icon(action) + + node = WorkflowNode( + id=node_id, + type=NodeType.ACTION, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": action_label, + "action": action, + "params": self._sanitize_params(params), + "icon": icon, + }, + status=status, + start_time=time.time() if status == NodeStatus.RUNNING else None, + ) + + self.nodes.append(node) + + # Add edge from parent + edge = WorkflowEdge(id=f"edge_{parent_id}_{node_id}", source=parent_id, target=node_id) + self.edges.append(edge) + + self.current_depth += 1 + return node_id + + def add_result_node(self, parent_id: str, result: Any, success: bool = True) -> str: + """Add a result node.""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = ( + parent_node.position["y"] + self.vertical_spacing + if parent_node + else self.vertical_spacing + ) + + node = WorkflowNode( + id=node_id, + type=NodeType.RESULT, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": "Success" if success else "Failed", + "result": str(result)[:200] if result else "No result", + "full_result": str(result) if result else None, + "icon": "✅" if success else "❌", + }, + status=NodeStatus.COMPLETED if success else NodeStatus.ERROR, + start_time=time.time(), + end_time=time.time(), + ) + + self.nodes.append(node) + + # Add edge from parent + edge = WorkflowEdge( + id=f"edge_{parent_id}_{node_id}", + source=parent_id, + target=node_id, + label="✓" if success else "✗", + ) + self.edges.append(edge) + + return node_id + + def add_error_node(self, parent_id: str, error: Exception | str) -> str: + """Add an error node.""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = ( + parent_node.position["y"] + self.vertical_spacing + if parent_node + else self.vertical_spacing + ) + + error_msg = ( + str(error) if isinstance(error, str) else f"{type(error).__name__}: {str(error)}" + ) + + node = WorkflowNode( + id=node_id, + type=NodeType.ERROR, + position={"x": self.horizontal_offset, "y": y_pos}, + data={ + "label": "Error", + "error": error_msg[:200], + "full_error": error_msg, + "icon": "🚫", + }, + status=NodeStatus.ERROR, + start_time=time.time(), + end_time=time.time(), + ) + + self.nodes.append(node) + + # Add edge from parent + edge = WorkflowEdge( + id=f"edge_{parent_id}_{node_id}", source=parent_id, target=node_id, label="error" + ) + self.edges.append(edge) + + return node_id + + def add_end_node(self, parent_id: str, final_result: str | None = None) -> str: + """Add the ending node.""" + node_id = self._generate_node_id() + + parent_node = self._get_node_by_id(parent_id) + y_pos = ( + parent_node.position["y"] + self.vertical_spacing + if parent_node + else self.vertical_spacing + ) + + node = WorkflowNode( + id=node_id, + type=NodeType.END, + position={"x": self.horizontal_offset, "y": y_pos}, + data={"label": "Complete", "result": final_result or "Task completed", "icon": "🏁"}, + status=NodeStatus.COMPLETED, + start_time=time.time(), + end_time=time.time(), + ) + + self.nodes.append(node) + + # Add edge from parent + edge = WorkflowEdge(id=f"edge_{parent_id}_{node_id}", source=parent_id, target=node_id) + self.edges.append(edge) + + return node_id + + def update_node_status( + self, node_id: str, status: NodeStatus, duration: float | None = None, result: Any = None + ): + """Update a node's status.""" + node = self._get_node_by_id(node_id) + if node: + node.status = status + + # Update timing + if status == NodeStatus.RUNNING and not node.start_time: + node.start_time = time.time() + elif status in (NodeStatus.COMPLETED, NodeStatus.ERROR): + node.end_time = time.time() + + # Update result/data + if result is not None: + node.data["result"] = str(result)[:200] + node.data["full_result"] = str(result) + + if duration is not None: + node.data["duration"] = duration + + def to_dict(self) -> dict[str, Any]: + """Convert to dict for Gradio component.""" + return { + "nodes": [node.to_dict() for node in self.nodes], + "edges": [edge.to_dict() for edge in self.edges], + "metadata": { + "total_nodes": len(self.nodes), + "total_edges": len(self.edges), + "depth": self.current_depth, + }, + } + + def to_json(self) -> str: + """Convert to JSON string.""" + import json + + return json.dumps(self.to_dict(), indent=2) + + def _generate_node_id(self) -> str: + """Generate a unique node ID.""" + node_id = f"node_{self.node_counter}" + self.node_counter += 1 + return node_id + + def _get_node_by_id(self, node_id: str) -> WorkflowNode | None: + """Get a node by its ID.""" + return next((n for n in self.nodes if n.id == node_id), None) + + def _format_action_label(self, action: str) -> str: + """Format action name for display.""" + # Remove common prefixes + action = action.replace("go_to_", "").replace("extract_", "") + + # Convert snake_case to Title Case + words = action.split("_") + return " ".join(word.capitalize() for word in words) + + def _get_action_icon(self, action: str) -> str: + """Get appropriate icon for action type.""" + action_lower = action.lower() + + if "navigate" in action_lower or "go_to" in action_lower: + return "🧭" + elif "click" in action_lower: + return "🖱️" + elif "type" in action_lower or "input" in action_lower: + return "⌨️" + elif "extract" in action_lower or "get" in action_lower: + return "📊" + elif "search" in action_lower: + return "🔍" + elif "scroll" in action_lower: + return "📜" + elif "screenshot" in action_lower: + return "📸" + elif "wait" in action_lower: + return "⏱️" + else: + return "⚡" + + def _sanitize_params(self, params: dict[str, Any]) -> dict[str, Any]: + """Sanitize parameters for display (remove sensitive data, truncate long values).""" + sanitized = {} + + for key, value in params.items(): + # Skip sensitive keys + if any( + sensitive in key.lower() for sensitive in ["password", "token", "secret", "key"] + ): + sanitized[key] = "***REDACTED***" + # Truncate long values + elif isinstance(value, str) and len(value) > 100: + sanitized[key] = value[:97] + "..." + else: + sanitized[key] = value + + return sanitized diff --git a/src/web_ui/webui/__init__.py b/src/web_ui/webui/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/web_ui/webui/components/__init__.py b/src/web_ui/webui/components/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/web_ui/webui/components/agent_settings_tab.py b/src/web_ui/webui/components/agent_settings_tab.py new file mode 100644 index 00000000..a1cf97dc --- /dev/null +++ b/src/web_ui/webui/components/agent_settings_tab.py @@ -0,0 +1,341 @@ +import json +import logging +import os + +import gradio as gr + +from src.web_ui.utils import config +from src.web_ui.utils.mcp_config import get_mcp_config_path, get_mcp_config_summary, load_mcp_config +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + + +def update_model_dropdown(llm_provider): + """ + Update the model name dropdown with predefined models for the selected provider. + """ + # Use predefined models for the selected provider + if llm_provider in config.model_names: + models = config.model_names[llm_provider] + return gr.update( + choices=models, + value=models[0] if models else "", + interactive=True, + ) + else: + return gr.update(choices=[], value="", interactive=True) + + +async def update_mcp_server(mcp_file: str, webui_manager: WebuiManager): + """ + Update the MCP server. + """ + if hasattr(webui_manager, "bu_controller") and webui_manager.bu_controller: + logger.warning("⚠️ Close controller because mcp file has changed!") + await webui_manager.bu_controller.close_mcp_client() + webui_manager.bu_controller = None + + if not mcp_file or not os.path.exists(mcp_file) or not mcp_file.endswith(".json"): + logger.warning(f"{mcp_file} is not a valid MCP file.") + return None, gr.update(visible=False) + + with open(mcp_file) as f: + mcp_server = json.load(f) + + return json.dumps(mcp_server, indent=2), gr.update(visible=True) + + +def create_agent_settings_tab(webui_manager: WebuiManager): + """ + Creates an agent settings tab with improved organization using accordions. + """ + tab_components = {} + + # System Prompts Section + with gr.Accordion("📝 System Prompts", open=False): + gr.Markdown("Customize agent behavior with custom system prompts.") + with gr.Column(): + override_system_prompt = gr.Textbox( + label="Override System Prompt", + lines=4, + interactive=True, + placeholder="Replace the entire system prompt with your own...", + ) + extend_system_prompt = gr.Textbox( + label="Extend System Prompt", + lines=4, + interactive=True, + placeholder="Add additional instructions to the default prompt...", + ) + + # MCP Configuration Section + with gr.Accordion("🔌 MCP Configuration", open=False): + gr.Markdown("Model Context Protocol server configuration.") + + # Check if mcp.json exists and show status + mcp_config_path = get_mcp_config_path() + mcp_file_exists = mcp_config_path.exists() + mcp_file_config = load_mcp_config() if mcp_file_exists else None + + if mcp_file_exists and mcp_file_config: + status_md = f""" +✅ **Using MCP configuration from file:** `{mcp_config_path}` + +{get_mcp_config_summary(mcp_file_config)} + +To edit MCP settings, go to the **MCP Settings** tab or edit `{mcp_config_path}` directly. +""" + else: + status_md = f""" +ℹ️ No MCP configuration file found at `{mcp_config_path}` + +You can: +- Upload a JSON file below (temporary, per-session) +- Go to the **MCP Settings** tab to create and edit a persistent configuration +""" + + mcp_file_status = gr.Markdown(status_md) + + mcp_json_file = gr.File( + label="MCP server json (Upload for temporary override)", + interactive=True, + file_types=[".json"], + visible=not mcp_file_exists, # Hide if file already exists + ) + mcp_server_config = gr.Textbox( + label="MCP server configuration", lines=6, interactive=True, visible=False + ) + + # Primary LLM Configuration + with gr.Accordion("🤖 Primary LLM Configuration", open=True): + gr.Markdown("**Main language model** used for agent reasoning and actions.") + + with gr.Row(): + llm_provider = gr.Dropdown( + choices=[provider for provider, model in config.model_names.items()], + label="LLM Provider", + value=os.getenv("DEFAULT_LLM", "openai"), + info="Select LLM provider", + interactive=True, + ) + llm_model_name = gr.Dropdown( + label="Model Name", + choices=config.model_names[os.getenv("DEFAULT_LLM", "openai")], + value=config.model_names[os.getenv("DEFAULT_LLM", "openai")][0], + interactive=True, + allow_custom_value=True, + info="Select or type custom model name", + ) + + with gr.Row(): + llm_temperature = gr.Slider( + minimum=0.0, + maximum=2.0, + value=0.6, + step=0.1, + label="Temperature", + info="Controls randomness (0=deterministic, 2=creative)", + interactive=True, + ) + + use_vision = gr.Checkbox( + label="Enable Vision", + value=True, + info="Input screenshots to LLM for better context", + interactive=True, + ) + + ollama_num_ctx = gr.Slider( + minimum=2**8, + maximum=2**16, + value=16000, + step=1, + label="Ollama Context Length", + info="Max context length (less = faster)", + visible=False, + interactive=True, + ) + + with gr.Accordion("🔑 API Credentials (Optional)", open=False): + gr.Markdown("Override environment variables with custom credentials.") + with gr.Row(): + llm_base_url = gr.Textbox( + label="Base URL", + value="", + info="Custom API endpoint (leave blank for default)", + placeholder="https://api.example.com/v1", + ) + llm_api_key = gr.Textbox( + label="API Key", + type="password", + value="", + info="Leave blank to use .env file", + placeholder="sk-...", + ) + + # Planner LLM Configuration (Optional) + with gr.Accordion("🧠 Planner LLM Configuration (Optional)", open=False): + gr.Markdown(""" + **Separate planning model** for complex multi-step reasoning. + + 💡 Leave empty to use the same model for both planning and execution. + """) + + with gr.Row(): + planner_llm_provider = gr.Dropdown( + choices=[provider for provider, model in config.model_names.items()], + label="Planner Provider", + info="Optional separate provider for planning", + value=None, + interactive=True, + ) + planner_llm_model_name = gr.Dropdown( + label="Planner Model", + interactive=True, + allow_custom_value=True, + info="Select or type custom model name", + ) + + with gr.Row(): + planner_llm_temperature = gr.Slider( + minimum=0.0, + maximum=2.0, + value=0.6, + step=0.1, + label="Temperature", + info="Planning temperature (lower = more focused)", + interactive=True, + ) + + planner_use_vision = gr.Checkbox( + label="Enable Vision", + value=False, + info="Enable vision for planner", + interactive=True, + ) + + planner_ollama_num_ctx = gr.Slider( + minimum=2**8, + maximum=2**16, + value=16000, + step=1, + label="Ollama Context", + info="Max context for Ollama", + visible=False, + interactive=True, + ) + + with gr.Accordion("🔑 Planner API Credentials (Optional)", open=False): + with gr.Row(): + planner_llm_base_url = gr.Textbox( + label="Base URL", + value="", + info="Custom API endpoint", + placeholder="https://api.example.com/v1", + ) + planner_llm_api_key = gr.Textbox( + label="API Key", + type="password", + value="", + info="Leave blank to use .env", + placeholder="sk-...", + ) + + # Advanced Agent Parameters + with gr.Accordion("⚡ Advanced Parameters", open=False): + gr.Markdown("**Fine-tune agent behavior** and performance limits.") + + with gr.Row(): + max_steps = gr.Slider( + minimum=1, + maximum=1000, + value=100, + step=1, + label="Max Steps", + info="Maximum reasoning steps before stopping", + interactive=True, + ) + max_actions = gr.Slider( + minimum=1, + maximum=100, + value=10, + step=1, + label="Max Actions per Step", + info="Actions per reasoning step", + interactive=True, + ) + + with gr.Row(): + max_input_tokens = gr.Number( + label="Max Input Tokens", + value=128000, + precision=0, + interactive=True, + info="Context window limit", + ) + tool_calling_method = gr.Dropdown( + label="Tool Calling Method", + value="auto", + interactive=True, + allow_custom_value=True, + choices=["function_calling", "json_mode", "raw", "auto", "tools", "None"], + info="Auto-detect recommended", + visible=True, + ) + tab_components.update( + { + "override_system_prompt": override_system_prompt, + "extend_system_prompt": extend_system_prompt, + "llm_provider": llm_provider, + "llm_model_name": llm_model_name, + "llm_temperature": llm_temperature, + "use_vision": use_vision, + "ollama_num_ctx": ollama_num_ctx, + "llm_base_url": llm_base_url, + "llm_api_key": llm_api_key, + "planner_llm_provider": planner_llm_provider, + "planner_llm_model_name": planner_llm_model_name, + "planner_llm_temperature": planner_llm_temperature, + "planner_use_vision": planner_use_vision, + "planner_ollama_num_ctx": planner_ollama_num_ctx, + "planner_llm_base_url": planner_llm_base_url, + "planner_llm_api_key": planner_llm_api_key, + "max_steps": max_steps, + "max_actions": max_actions, + "max_input_tokens": max_input_tokens, + "tool_calling_method": tool_calling_method, + "mcp_file_status": mcp_file_status, + "mcp_json_file": mcp_json_file, + "mcp_server_config": mcp_server_config, + } + ) + webui_manager.add_components("agent_settings", tab_components) + + llm_provider.change( + fn=lambda x: gr.update(visible=x == "ollama"), inputs=llm_provider, outputs=ollama_num_ctx + ) + llm_provider.change( + lambda provider: update_model_dropdown(provider), + inputs=[llm_provider], + outputs=[llm_model_name], + ) + planner_llm_provider.change( + fn=lambda x: gr.update(visible=x == "ollama"), + inputs=[planner_llm_provider], + outputs=[planner_ollama_num_ctx], + ) + planner_llm_provider.change( + lambda provider: update_model_dropdown(provider), + inputs=[planner_llm_provider], + outputs=[planner_llm_model_name], + ) + + async def update_wrapper(mcp_file): + """Wrapper for handle_pause_resume.""" + update_dict = await update_mcp_server(mcp_file, webui_manager) + yield update_dict + + mcp_json_file.change( + update_wrapper, inputs=[mcp_json_file], outputs=[mcp_server_config, mcp_server_config] + ) diff --git a/src/web_ui/webui/components/browser_settings_tab.py b/src/web_ui/webui/components/browser_settings_tab.py new file mode 100644 index 00000000..ce3f0991 --- /dev/null +++ b/src/web_ui/webui/components/browser_settings_tab.py @@ -0,0 +1,205 @@ +import logging +import os + +import gradio as gr + +from src.web_ui.webui.webui_manager import WebuiManager + + +def strtobool(val): + """Convert a string representation of truth to true (1) or false (0). + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values + are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if + 'val' is anything else. + """ + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return 1 + elif val in ("n", "no", "f", "false", "off", "0"): + return 0 + else: + raise ValueError(f"invalid truth value {val!r}") + + +logger = logging.getLogger(__name__) + + +async def close_browser(webui_manager: WebuiManager): + """ + Close browser + """ + if webui_manager.bu_current_task and not webui_manager.bu_current_task.done(): + webui_manager.bu_current_task.cancel() + webui_manager.bu_current_task = None + + if webui_manager.bu_browser_context: + logger.info("⚠️ Closing browser context when changing browser config.") + await webui_manager.bu_browser_context.close() + webui_manager.bu_browser_context = None + + if webui_manager.bu_browser: + logger.info("⚠️ Closing browser when changing browser config.") + await webui_manager.bu_browser.close() + webui_manager.bu_browser = None + + +def create_browser_settings_tab(webui_manager: WebuiManager): + """ + Creates a browser settings tab with improved organization. + """ + tab_components = {} + + # Custom Browser Configuration + with gr.Accordion("🌐 Custom Browser Configuration", open=False): + gr.Markdown(""" + **Use your own Chrome/browser** instead of Playwright's default browser. + + ⚠️ Close all Chrome windows before enabling "Use Own Browser" mode. + """) + + with gr.Row(): + use_own_browser = gr.Checkbox( + label="Use Own Browser", + value=bool(strtobool(os.getenv("USE_OWN_BROWSER", "false"))), + info="Connect to your existing browser instance", + interactive=True, + ) + keep_browser_open = gr.Checkbox( + label="Keep Browser Open", + value=bool(strtobool(os.getenv("KEEP_BROWSER_OPEN", "true"))), + info="Persist browser between tasks", + interactive=True, + ) + + with gr.Row(): + browser_binary_path = gr.Textbox( + label="Browser Binary Path", + lines=1, + interactive=True, + placeholder="e.g. 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'", + info="Path to Chrome/Chromium executable", + ) + browser_user_data_dir = gr.Textbox( + label="Browser User Data Directory", + lines=1, + interactive=True, + placeholder="Leave empty for default profile", + info="Custom profile directory", + ) + + # Browser Behavior Settings + with gr.Accordion("⚙️ Browser Behavior", open=True): + gr.Markdown("**Configure how the browser runs** and displays.") + + with gr.Row(): + headless = gr.Checkbox( + label="Headless Mode", + value=False, + info="Run browser without visible GUI (faster but no visual feedback)", + interactive=True, + ) + disable_security = gr.Checkbox( + label="Disable Security", + value=False, + info="⚠️ Disable browser security (use with caution)", + interactive=True, + ) + + with gr.Row(): + window_w = gr.Number( + label="Window Width", + value=1280, + info="Browser viewport width in pixels", + interactive=True, + ) + window_h = gr.Number( + label="Window Height", + value=1100, + info="Browser viewport height in pixels", + interactive=True, + ) + + # Remote Debugging Configuration + with gr.Accordion("🔗 Remote Debugging (Advanced)", open=False): + gr.Markdown(""" + **Connect to a remote browser** via Chrome DevTools Protocol or WebSocket. + + Use this for debugging or connecting to browsers running on different machines. + """) + + with gr.Row(): + cdp_url = gr.Textbox( + label="CDP URL", + value=os.getenv("BROWSER_CDP", None), + info="Chrome DevTools Protocol endpoint", + placeholder="http://localhost:9222", + interactive=True, + ) + wss_url = gr.Textbox( + label="WSS URL", + info="WebSocket Secure URL for remote debugging", + placeholder="wss://localhost:9222/devtools/browser/...", + interactive=True, + ) + + # Storage Paths Configuration + with gr.Accordion("💾 Storage Paths", open=False): + gr.Markdown("**Configure where files are saved** by the agent and browser.") + + with gr.Row(): + save_recording_path = gr.Textbox( + label="📹 Recording Path", + placeholder="./tmp/record_videos", + info="Browser screen recordings (GIF/MP4)", + interactive=True, + ) + + save_trace_path = gr.Textbox( + label="📊 Trace Path", + placeholder="./tmp/traces", + info="Agent execution traces for debugging", + interactive=True, + ) + + with gr.Row(): + save_agent_history_path = gr.Textbox( + label="📜 Agent History Path", + value="./tmp/agent_history", + info="Agent conversation and action history", + interactive=True, + ) + save_download_path = gr.Textbox( + label="⬇️ Downloads Path", + value="./tmp/downloads", + info="Files downloaded by the browser", + interactive=True, + ) + tab_components.update( + { + "browser_binary_path": browser_binary_path, + "browser_user_data_dir": browser_user_data_dir, + "use_own_browser": use_own_browser, + "keep_browser_open": keep_browser_open, + "headless": headless, + "disable_security": disable_security, + "save_recording_path": save_recording_path, + "save_trace_path": save_trace_path, + "save_agent_history_path": save_agent_history_path, + "save_download_path": save_download_path, + "cdp_url": cdp_url, + "wss_url": wss_url, + "window_h": window_h, + "window_w": window_w, + } + ) + webui_manager.add_components("browser_settings", tab_components) + + async def close_wrapper(): + """Wrapper for handle_clear.""" + await close_browser(webui_manager) + + headless.change(close_wrapper) + keep_browser_open.change(close_wrapper) + disable_security.change(close_wrapper) + use_own_browser.change(close_wrapper) diff --git a/src/web_ui/webui/components/browser_use_agent_tab.py b/src/web_ui/webui/components/browser_use_agent_tab.py new file mode 100644 index 00000000..7d174918 --- /dev/null +++ b/src/web_ui/webui/components/browser_use_agent_tab.py @@ -0,0 +1,1100 @@ +import asyncio +import json +import logging +import os +import uuid +from collections.abc import AsyncGenerator +from typing import Any + +import gradio as gr + +# from browser_use.agent.service import Agent +from browser_use.agent.views import ( + AgentHistoryList, + AgentOutput, +) +from browser_use.browser.browser import BrowserConfig +from browser_use.browser.context import BrowserContext, BrowserContextConfig + +# BrowserState is not available in browser_use.browser.views, using BrowserStateHistory instead +from browser_use.browser.views import BrowserStateHistory +from langchain_core.language_models.chat_models import BaseChatModel + +from src.web_ui.agent.browser_use.browser_use_agent import BrowserUseAgent +from src.web_ui.browser.custom_browser import CustomBrowser +from src.web_ui.controller.custom_controller import CustomController +from src.web_ui.utils import llm_provider +from src.web_ui.utils.mcp_config import get_mcp_config_path, load_mcp_config +from src.web_ui.webui.components.chat_formatter import ( + CHAT_FORMATTING_CSS, + CHAT_FORMATTING_JS, + format_agent_message, + format_error_message, +) +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + + +# --- Helper Functions --- (Defined at module level) + + +async def _initialize_llm( + provider: str | None, + model_name: str | None, + temperature: float, + base_url: str | None, + api_key: str | None, + num_ctx: int | None = None, +) -> BaseChatModel | None: + """Initializes the LLM based on settings. Returns None if provider/model is missing.""" + if not provider or not model_name: + logger.info("LLM Provider or Model Name not specified, LLM will be None.") + return None + try: + # Use your actual LLM provider logic here + logger.info( + f"Initializing LLM: Provider={provider}, Model={model_name}, Temp={temperature}" + ) + # Example using a placeholder function + llm = llm_provider.get_llm_model( + provider=provider, + model_name=model_name, + temperature=temperature, + base_url=base_url or None, + api_key=api_key or None, + # Add other relevant params like num_ctx for ollama + num_ctx=num_ctx if provider == "ollama" else None, + ) + return llm + except Exception as e: + logger.error(f"Failed to initialize LLM: {e}", exc_info=True) + gr.Warning( + f"Failed to initialize LLM '{model_name}' for provider '{provider}'. Please check settings. Error: {e}" + ) + return None + + +def _get_config_value( + webui_manager: WebuiManager, + comp_dict: dict[gr.components.Component, Any], + comp_id_suffix: str, + default: Any = None, +) -> Any: + """Safely get value from component dictionary using its ID suffix relative to the tab.""" + # Assumes component ID format is "tab_name.comp_name" + tab_name = "browser_use_agent" # Hardcode or derive if needed + comp_id = f"{tab_name}.{comp_id_suffix}" + # Need to find the component object first using the ID from the manager + try: + comp = webui_manager.get_component_by_id(comp_id) + return comp_dict.get(comp, default) + except KeyError: + # Try accessing settings tabs as well + for prefix in ["agent_settings", "browser_settings"]: + try: + comp_id = f"{prefix}.{comp_id_suffix}" + comp = webui_manager.get_component_by_id(comp_id) + return comp_dict.get(comp, default) + except KeyError: + continue + logger.warning( + f"Component with suffix '{comp_id_suffix}' not found in manager for value lookup." + ) + return default + + +def _format_agent_output(model_output: AgentOutput) -> str: + """Formats AgentOutput for display in the chatbot using JSON.""" + content = "" + if model_output: + try: + # Directly use model_dump if actions and current_state are Pydantic models + action_dump = [action.model_dump(exclude_none=True) for action in model_output.action] + + state_dump = model_output.current_state.model_dump(exclude_none=True) + model_output_dump = { + "current_state": state_dump, + "action": action_dump, + } + # Dump to JSON string with indentation + json_string = json.dumps(model_output_dump, indent=4, ensure_ascii=False) + # Wrap in
 for proper display in HTML
+            content = f"
{json_string}
" + + except AttributeError as ae: + logger.error( + f"AttributeError during model dump: {ae}. Check if 'action' or 'current_state' or their items support 'model_dump'." + ) + content = f"
Error: Could not format agent output (AttributeError: {ae}).\nRaw output: {str(model_output)}
" + except Exception as e: + logger.error(f"Error formatting agent output: {e}", exc_info=True) + # Fallback to simple string representation on error + content = f"
Error formatting agent output.\nRaw output:\n{str(model_output)}
" + + return content.strip() + + +# --- Updated Callback Implementation --- + + +async def _handle_new_step( + webui_manager: WebuiManager, state: BrowserStateHistory, output: AgentOutput, step_num: int +): + """Callback for each step taken by the agent, including screenshot display and formatted messages.""" + + # Use the correct chat history attribute name from the user's code + if not hasattr(webui_manager, "bu_chat_history"): + logger.error( + "Attribute 'bu_chat_history' not found in webui_manager! Cannot add chat message." + ) + # Initialize it maybe? Or raise an error? For now, log and potentially skip chat update. + webui_manager.bu_chat_history = [] # Initialize if missing (consider if this is the right place) + # return # Or stop if this is critical + step_num -= 1 + logger.info(f"Step {step_num} completed.") + + # --- Screenshot Handling --- + screenshot_html = "" + # Ensure state.screenshot exists and is not empty before proceeding + # Use getattr for safer access + screenshot_data = getattr(state, "screenshot", None) + if screenshot_data: + try: + # Basic validation: check if it looks like base64 + if ( + isinstance(screenshot_data, str) and len(screenshot_data) > 100 + ): # Arbitrary length check + # *** UPDATED STYLE: Removed centering, adjusted width *** + img_tag = f'Step {step_num} Screenshot' + screenshot_html = ( + img_tag + "
" + ) # Use
for line break after inline-block image + else: + logger.warning( + f"Screenshot for step {step_num} seems invalid (type: {type(screenshot_data)}, len: {len(screenshot_data) if isinstance(screenshot_data, str) else 'N/A'})." + ) + screenshot_html = "**[Invalid screenshot data]**
" + + except Exception as e: + logger.error( + f"Error processing or formatting screenshot for step {step_num}: {e}", + exc_info=True, + ) + screenshot_html = "**[Error displaying screenshot]**
" + else: + logger.debug(f"No screenshot available for step {step_num}.") + + # --- Format Agent Output with Enhanced Styling --- + formatted_output = _format_agent_output(output) # Use the updated function + + # Extract action information for badge if available + metadata = {} + if output and hasattr(output, "current_state") and output.current_state: + action_model = ( + output.current_state.action_model + if hasattr(output.current_state, "action_model") + else None + ) + if action_model and hasattr(action_model, "action"): + metadata["action"] = action_model.action + metadata["status"] = "completed" + + # Apply rich formatting to the output + formatted_output = format_agent_message(formatted_output, metadata) + + # --- Combine and Append to Chat --- + step_header = f"--- **Step {step_num}** ---" + # Combine header, image (with line break), and formatted output + final_content = step_header + "
" + screenshot_html + formatted_output + + chat_message = { + "role": "assistant", + "content": final_content.strip(), # Remove leading/trailing whitespace + } + + # Append to the correct chat history list + webui_manager.bu_chat_history.append(chat_message) + + await asyncio.sleep(0.05) + + +def _handle_done(webui_manager: WebuiManager, history: AgentHistoryList): + """Callback when the agent finishes the task (success or failure).""" + logger.info(f"Agent task finished. Duration: {history.total_duration_seconds():.2f}s") + final_summary = "**Task Completed**\n" + final_summary += f"- Duration: {history.total_duration_seconds():.2f} seconds\n" + + final_result = history.final_result() + if final_result: + final_summary += f"- Final Result: {final_result}\n" + + errors = history.errors() + if errors and any(errors): + final_summary += f"- **Errors:**\n```\n{errors}\n```\n" + else: + final_summary += "- Status: Success\n" + + webui_manager.bu_chat_history.append({"role": "assistant", "content": final_summary}) + + +async def _ask_assistant_callback( + webui_manager: WebuiManager, query: str, browser_context: BrowserContext +) -> dict[str, Any]: + """Callback triggered by the agent's ask_for_assistant action.""" + logger.info("Agent requires assistance. Waiting for user input.") + + if not hasattr(webui_manager, "_chat_history"): + logger.error("Chat history not found in webui_manager during ask_assistant!") + return {"response": "Internal Error: Cannot display help request."} + + webui_manager.bu_chat_history.append( + { + "role": "assistant", + "content": f"**Need Help:** {query}\nPlease provide information or perform the required action in the browser, then type your response/confirmation below and click 'Submit Response'.", + } + ) + + # Use state stored in webui_manager + webui_manager.bu_response_event = asyncio.Event() + webui_manager.bu_user_help_response = None # Reset previous response + + try: + logger.info("Waiting for user response event...") + await asyncio.wait_for( + webui_manager.bu_response_event.wait(), timeout=3600.0 + ) # Long timeout + logger.info("User response event received.") + except TimeoutError: + logger.warning("Timeout waiting for user assistance.") + webui_manager.bu_chat_history.append( + { + "role": "assistant", + "content": "**Timeout:** No response received. Trying to proceed.", + } + ) + webui_manager.bu_response_event = None # Clear the event + return {"response": "Timeout: User did not respond."} # Inform the agent + + response = webui_manager.bu_user_help_response + webui_manager.bu_chat_history.append( + {"role": "user", "content": response} + ) # Show user response in chat + webui_manager.bu_response_event = None # Clear the event for the next potential request + return {"response": response} + + +# --- Core Agent Execution Logic --- (Needs access to webui_manager) + + +async def run_agent_task( + webui_manager: WebuiManager, components: dict[gr.components.Component, Any] +) -> AsyncGenerator[dict[gr.components.Component, Any]]: + """Handles the entire lifecycle of initializing and running the agent.""" + + # --- Get Components --- + # Need handles to specific UI components to update them + progress_text_comp = webui_manager.get_component_by_id("browser_use_agent.progress_text") + user_input_comp = webui_manager.get_component_by_id("browser_use_agent.user_input") + run_button_comp = webui_manager.get_component_by_id("browser_use_agent.run_button") + stop_button_comp = webui_manager.get_component_by_id("browser_use_agent.stop_button") + pause_resume_button_comp = webui_manager.get_component_by_id( + "browser_use_agent.pause_resume_button" + ) + clear_button_comp = webui_manager.get_component_by_id("browser_use_agent.clear_button") + chatbot_comp = webui_manager.get_component_by_id("browser_use_agent.chatbot") + history_file_comp = webui_manager.get_component_by_id("browser_use_agent.agent_history_file") + gif_comp = webui_manager.get_component_by_id("browser_use_agent.recording_gif") + browser_view_comp = webui_manager.get_component_by_id("browser_use_agent.browser_view") + + # --- 1. Get Task and Initial UI Update --- + task = components.get(user_input_comp, "").strip() + if not task: + gr.Warning("Please enter a task.") + yield {run_button_comp: gr.update(interactive=True)} + return + + # Set running state indirectly via _current_task + webui_manager.bu_chat_history.append({"role": "user", "content": task}) + + yield { + progress_text_comp: gr.update(value="🔄 **Initializing agent...**"), + user_input_comp: gr.Textbox(value="", interactive=False, placeholder="Agent is running..."), + run_button_comp: gr.Button(value="⏳ Running...", interactive=False), + stop_button_comp: gr.Button(interactive=True), + pause_resume_button_comp: gr.Button(value="⏸️ Pause", interactive=True), + clear_button_comp: gr.Button(interactive=False), + chatbot_comp: gr.update(value=webui_manager.bu_chat_history), + history_file_comp: gr.update(value=None), + gif_comp: gr.update(value=None), + } + + # --- Agent Settings --- + # Access settings values via components dict, getting IDs from webui_manager + def get_setting(key, default=None): + # Try dashboard_settings first (primary namespace), then fall back to agent_settings + comp = webui_manager.id_to_component.get(f"dashboard_settings.{key}") + if comp: + return components.get(comp, default) + # Fallback to agent_settings for backward compatibility + comp = webui_manager.id_to_component.get(f"agent_settings.{key}") + return components.get(comp, default) if comp else default + + override_system_prompt = get_setting("override_system_prompt") or None + extend_system_prompt = get_setting("extend_system_prompt") or None + llm_provider_name = get_setting("llm_provider", None) # Default to None if not found + llm_model_name = get_setting("llm_model_name", None) + llm_temperature = get_setting("llm_temperature", 0.6) + use_vision = get_setting("use_vision", True) + ollama_num_ctx = get_setting("ollama_num_ctx", 16000) + llm_base_url = get_setting("llm_base_url") or None + llm_api_key = get_setting("llm_api_key") or None + max_steps = get_setting("max_steps", 100) + max_actions = get_setting("max_actions", 10) + max_input_tokens = get_setting("max_input_tokens", 128000) + tool_calling_str = get_setting("tool_calling_method", "auto") + tool_calling_method = tool_calling_str if tool_calling_str != "None" else None + # Load MCP configuration - prioritize file over UI + mcp_server_config = None + + # First, try to load from data/mcp.json file + file_config = load_mcp_config() + if file_config: + mcp_server_config = file_config + logger.info(f"Loaded MCP configuration from {get_mcp_config_path()}") + + # If no file config, fall back to UI textbox + if mcp_server_config is None: + mcp_server_config_comp = webui_manager.id_to_component.get( + "agent_settings.mcp_server_config" + ) + mcp_server_config_str = ( + components.get(mcp_server_config_comp) if mcp_server_config_comp else None + ) + if mcp_server_config_str: + try: + mcp_server_config = json.loads(mcp_server_config_str) + logger.info("Loaded MCP configuration from UI textbox") + except json.JSONDecodeError as e: + logger.error(f"Failed to parse MCP config from UI: {e}") + gr.Warning(f"Invalid MCP configuration JSON in UI: {e}") + mcp_server_config = None + + # Planner LLM Settings (Optional) + planner_llm_provider_name = get_setting("planner_llm_provider") or None + planner_llm = None + planner_use_vision = False + if planner_llm_provider_name: + planner_llm_model_name = get_setting("planner_llm_model_name") + planner_llm_temperature = get_setting("planner_llm_temperature", 0.6) + planner_ollama_num_ctx = get_setting("planner_ollama_num_ctx", 16000) + planner_llm_base_url = get_setting("planner_llm_base_url") or None + planner_llm_api_key = get_setting("planner_llm_api_key") or None + planner_use_vision = get_setting("planner_use_vision", False) + + planner_llm = await _initialize_llm( + planner_llm_provider_name, + planner_llm_model_name, + planner_llm_temperature, + planner_llm_base_url, + planner_llm_api_key, + planner_ollama_num_ctx if planner_llm_provider_name == "ollama" else None, + ) + + # --- Browser Settings --- + def get_browser_setting(key, default=None): + comp = webui_manager.id_to_component.get(f"browser_settings.{key}") + return components.get(comp, default) if comp else default + + browser_binary_path = get_browser_setting("browser_binary_path") or None + browser_user_data_dir = get_browser_setting("browser_user_data_dir") or None + use_own_browser = get_browser_setting( + "use_own_browser", False + ) # Logic handled by CDP/WSS presence + keep_browser_open = get_browser_setting("keep_browser_open", False) + headless = get_browser_setting("headless", False) + disable_security = get_browser_setting("disable_security", False) + window_w = int(get_browser_setting("window_w", 1280)) + window_h = int(get_browser_setting("window_h", 1100)) + cdp_url = get_browser_setting("cdp_url") or None + wss_url = get_browser_setting("wss_url") or None + save_recording_path = get_browser_setting("save_recording_path") or None + save_trace_path = get_browser_setting("save_trace_path") or None + save_agent_history_path = get_browser_setting("save_agent_history_path", "./tmp/agent_history") + save_download_path = get_browser_setting("save_download_path", "./tmp/downloads") + + stream_vw = 70 + stream_vh = int(70 * window_h // window_w) + + os.makedirs(save_agent_history_path, exist_ok=True) + if save_recording_path: + os.makedirs(save_recording_path, exist_ok=True) + if save_trace_path: + os.makedirs(save_trace_path, exist_ok=True) + if save_download_path: + os.makedirs(save_download_path, exist_ok=True) + + # --- 2. Initialize LLM --- + main_llm = await _initialize_llm( + llm_provider_name, + llm_model_name, + llm_temperature, + llm_base_url, + llm_api_key, + ollama_num_ctx if llm_provider_name == "ollama" else None, + ) + + # Pass the webui_manager instance to the callback when wrapping it + async def ask_callback_wrapper(query: str, browser_context: BrowserContext) -> dict[str, Any]: + return await _ask_assistant_callback(webui_manager, query, browser_context) + + if not webui_manager.bu_controller: + webui_manager.bu_controller = CustomController(ask_assistant_callback=ask_callback_wrapper) + await webui_manager.bu_controller.setup_mcp_client(mcp_server_config) + + # --- 4. Initialize Browser and Context --- + should_close_browser_on_finish = not keep_browser_open + + try: + # Close existing resources if not keeping open + if not keep_browser_open: + if webui_manager.bu_browser_context: + logger.info("Closing previous browser context.") + await webui_manager.bu_browser_context.close() + webui_manager.bu_browser_context = None + if webui_manager.bu_browser: + logger.info("Closing previous browser.") + await webui_manager.bu_browser.close() + webui_manager.bu_browser = None + + # Create Browser if needed + if not webui_manager.bu_browser: + logger.info("Launching new browser instance.") + extra_args = [] + if use_own_browser: + browser_binary_path = os.getenv("BROWSER_PATH", None) or browser_binary_path + if browser_binary_path == "": + browser_binary_path = None + browser_user_data = browser_user_data_dir or os.getenv("BROWSER_USER_DATA", None) + if browser_user_data: + extra_args += [f"--user-data-dir={browser_user_data}"] + else: + browser_binary_path = None + + webui_manager.bu_browser = CustomBrowser( + config=BrowserConfig( + headless=headless, + disable_security=disable_security, + browser_binary_path=browser_binary_path, + extra_browser_args=extra_args, + wss_url=wss_url, + cdp_url=cdp_url, + new_context_config=BrowserContextConfig( + window_width=window_w, + window_height=window_h, + ), + ) + ) + + # Create Context if needed + if not webui_manager.bu_browser_context: + logger.info("Creating new browser context.") + context_config = BrowserContextConfig( + trace_path=save_trace_path if save_trace_path else None, + save_recording_path=save_recording_path if save_recording_path else None, + save_downloads_path=save_download_path if save_download_path else None, + window_height=window_h, + window_width=window_w, + ) + if not webui_manager.bu_browser: + raise ValueError("Browser not initialized, cannot create context.") + webui_manager.bu_browser_context = await webui_manager.bu_browser.new_context( + config=context_config + ) + + # --- 5. Initialize or Update Agent --- + webui_manager.bu_agent_task_id = str(uuid.uuid4()) # New ID for this task run + os.makedirs( + os.path.join(save_agent_history_path, webui_manager.bu_agent_task_id), + exist_ok=True, + ) + history_file = os.path.join( + save_agent_history_path, + webui_manager.bu_agent_task_id, + f"{webui_manager.bu_agent_task_id}.json", + ) + gif_path = os.path.join( + save_agent_history_path, + webui_manager.bu_agent_task_id, + f"{webui_manager.bu_agent_task_id}.gif", + ) + + # Pass the webui_manager to callbacks when wrapping them + async def step_callback_wrapper( + state: BrowserStateHistory, output: AgentOutput, step_num: int + ): + await _handle_new_step(webui_manager, state, output, step_num) + + def done_callback_wrapper(history: AgentHistoryList): + _handle_done(webui_manager, history) + + if not webui_manager.bu_agent: + logger.info(f"Initializing new agent for task: {task}") + if not webui_manager.bu_browser or not webui_manager.bu_browser_context: + raise ValueError("Browser or Context not initialized, cannot create agent.") + webui_manager.bu_agent = BrowserUseAgent( + task=task, + llm=main_llm, + browser=webui_manager.bu_browser, + browser_context=webui_manager.bu_browser_context, + controller=webui_manager.bu_controller, + register_new_step_callback=step_callback_wrapper, + register_done_callback=done_callback_wrapper, + use_vision=use_vision, + override_system_message=override_system_prompt, + extend_system_message=extend_system_prompt, + max_input_tokens=max_input_tokens, + max_actions_per_step=max_actions, + tool_calling_method=tool_calling_method, + planner_llm=planner_llm, + use_vision_for_planner=planner_use_vision if planner_llm else False, + source="webui", + ) + webui_manager.bu_agent.state.agent_id = webui_manager.bu_agent_task_id + webui_manager.bu_agent.settings.generate_gif = gif_path + else: + webui_manager.bu_agent.state.agent_id = webui_manager.bu_agent_task_id + webui_manager.bu_agent.add_new_task(task) + webui_manager.bu_agent.settings.generate_gif = gif_path + webui_manager.bu_agent.browser = webui_manager.bu_browser + webui_manager.bu_agent.browser_context = webui_manager.bu_browser_context + webui_manager.bu_agent.controller = webui_manager.bu_controller + + # --- 6. Run Agent Task and Stream Updates --- + agent_run_coro = webui_manager.bu_agent.run(max_steps=max_steps) + agent_task = asyncio.create_task(agent_run_coro) + webui_manager.bu_current_task = agent_task # Store the task + + # Yield progress update + yield { + progress_text_comp: gr.update( + value=f"🤖 **Agent running** | Task: {task[:50]}{'...' if len(task) > 50 else ''}" + ), + } + + last_chat_len = len(webui_manager.bu_chat_history) + step_count = 0 + while not agent_task.done(): + is_paused = webui_manager.bu_agent.state.paused + is_stopped = webui_manager.bu_agent.state.stopped + + # Check for pause state + if is_paused: + yield { + progress_text_comp: gr.update(value="⏸️ **Paused** | Waiting for resume..."), + pause_resume_button_comp: gr.update(value="▶️ Resume", interactive=True), + stop_button_comp: gr.update(interactive=True), + } + # Wait until pause is released or task is stopped/done + while is_paused and not agent_task.done(): + # Re-check agent state in loop + is_paused = webui_manager.bu_agent.state.paused + is_stopped = webui_manager.bu_agent.state.stopped + if is_stopped: # Stop signal received while paused + break + await asyncio.sleep(0.2) + + if agent_task.done() or is_stopped: # If stopped or task finished while paused + break + + # If resumed, yield UI update + yield { + pause_resume_button_comp: gr.update(value="⏸️ Pause", interactive=True), + run_button_comp: gr.update(value="⏳ Running...", interactive=False), + } + + # Check if agent stopped itself or stop button was pressed (which sets agent.state.stopped) + if is_stopped: + logger.info("Agent has stopped (internally or via stop button).") + if not agent_task.done(): + # Ensure the task coroutine finishes if agent just set flag + try: + await asyncio.wait_for( + agent_task, timeout=1.0 + ) # Give it a moment to exit run() + except TimeoutError: + logger.warning( + "Agent task did not finish quickly after stop signal, cancelling." + ) + agent_task.cancel() + except Exception: # Catch task exceptions if it errors on stop + pass + break # Exit the streaming loop + + # Check if agent is asking for help (via response_event) + update_dict = {} + if webui_manager.bu_response_event is not None: + update_dict = { + user_input_comp: gr.update( + placeholder="Agent needs help. Enter response and submit.", + interactive=True, + ), + run_button_comp: gr.update(value="✔️ Submit Response", interactive=True), + pause_resume_button_comp: gr.update(interactive=False), + stop_button_comp: gr.update(interactive=False), + chatbot_comp: gr.update(value=webui_manager.bu_chat_history), + } + last_chat_len = len(webui_manager.bu_chat_history) + yield update_dict + # Wait until response is submitted or task finishes + await webui_manager.bu_response_event.wait() + + # Restore UI after response submitted or if task ended unexpectedly + if not agent_task.done(): + yield { + user_input_comp: gr.update( + placeholder="Agent is running...", interactive=False + ), + run_button_comp: gr.update(value="⏳ Running...", interactive=False), + pause_resume_button_comp: gr.update(interactive=True), + stop_button_comp: gr.update(interactive=True), + } + else: + break # Task finished while waiting for response + + # Update Chatbot if new messages arrived via callbacks + if len(webui_manager.bu_chat_history) > last_chat_len: + step_count += 1 + progress_msg = f"🤖 **Agent running** | Step {step_count}/{max_steps} | {len(webui_manager.bu_chat_history)} messages" + update_dict[progress_text_comp] = gr.update(value=progress_msg) + update_dict[chatbot_comp] = gr.update(value=webui_manager.bu_chat_history) + last_chat_len = len(webui_manager.bu_chat_history) + + # Update Browser View + if headless and webui_manager.bu_browser_context: + try: + screenshot_b64 = await webui_manager.bu_browser_context.take_screenshot() + if screenshot_b64: + html_content = f'' + update_dict[browser_view_comp] = gr.update(value=html_content, visible=True) + else: + html_content = f"

Waiting for browser session...

" + update_dict[browser_view_comp] = gr.update(value=html_content, visible=True) + except Exception as e: + logger.debug(f"Failed to capture screenshot: {e}") + update_dict[browser_view_comp] = gr.update( + value="
Error loading view...
", + visible=True, + ) + else: + update_dict[browser_view_comp] = gr.update(visible=False) + + # Yield accumulated updates + if update_dict: + yield update_dict + + await asyncio.sleep(0.1) # Polling interval + + # --- 7. Task Finalization --- + webui_manager.bu_agent.state.paused = False + webui_manager.bu_agent.state.stopped = False + final_update = {} + try: + logger.info("Agent task completing...") + # Await the task ensure completion and catch exceptions if not already caught + if not agent_task.done(): + await agent_task # Retrieve result/exception + elif agent_task.exception(): # Check if task finished with exception + agent_task.result() # Raise the exception to be caught below + logger.info("Agent task completed processing.") + + logger.info(f"Explicitly saving agent history to: {history_file}") + webui_manager.bu_agent.save_history(history_file) + + if os.path.exists(history_file): + final_update[history_file_comp] = gr.File(value=history_file) + + if gif_path and os.path.exists(gif_path): + logger.info(f"GIF found at: {gif_path}") + final_update[gif_comp] = gr.Image(value=gif_path) + + except asyncio.CancelledError: + logger.info("Agent task was cancelled.") + if not any( + "Cancelled" in (msg.get("content") or "") + for msg in webui_manager.bu_chat_history + if msg.get("role") == "assistant" + ): + webui_manager.bu_chat_history.append( + {"role": "assistant", "content": "**Task Cancelled**."} + ) + final_update[chatbot_comp] = gr.update(value=webui_manager.bu_chat_history) + except Exception as e: + logger.error(f"Error during agent execution: {e}", exc_info=True) + error_message = format_error_message( + e, context="Agent execution", include_traceback=True + ) + if not any( + "error-container" in (msg.get("content") or "") + for msg in webui_manager.bu_chat_history[-3:] # Check last 3 messages + if msg.get("role") == "assistant" + ): + webui_manager.bu_chat_history.append( + {"role": "assistant", "content": error_message} + ) + final_update[chatbot_comp] = gr.update(value=webui_manager.bu_chat_history) + gr.Error(f"Agent execution failed: {type(e).__name__}") + + finally: + webui_manager.bu_current_task = None # Clear the task reference + + # Close browser/context if requested + if should_close_browser_on_finish: + if webui_manager.bu_browser_context: + logger.info("Closing browser context after task.") + await webui_manager.bu_browser_context.close() + webui_manager.bu_browser_context = None + if webui_manager.bu_browser: + logger.info("Closing browser after task.") + await webui_manager.bu_browser.close() + webui_manager.bu_browser = None + + # --- 8. Final UI Update --- + final_update.update( + { + progress_text_comp: gr.update(value="✅ **Task completed successfully!**"), + user_input_comp: gr.update( + value="", + interactive=True, + placeholder="Enter your next task...", + ), + run_button_comp: gr.update(value="▶️ Submit Task", interactive=True), + stop_button_comp: gr.update(value="⏹️ Stop", interactive=False), + pause_resume_button_comp: gr.update(value="⏸️ Pause", interactive=False), + clear_button_comp: gr.update(interactive=True), + # Ensure final chat history is shown + chatbot_comp: gr.update(value=webui_manager.bu_chat_history), + } + ) + yield final_update + + except Exception as e: + # Catch errors during setup (before agent run starts) + logger.error(f"Error setting up agent task: {e}", exc_info=True) + webui_manager.bu_current_task = None # Ensure state is reset + yield { + progress_text_comp: gr.update(value=f"❌ **Error:** {str(e)[:100]}"), + user_input_comp: gr.update( + interactive=True, placeholder="Error during setup. Enter task..." + ), + run_button_comp: gr.update(value="▶️ Submit Task", interactive=True), + stop_button_comp: gr.update(value="⏹️ Stop", interactive=False), + pause_resume_button_comp: gr.update(value="⏸️ Pause", interactive=False), + clear_button_comp: gr.update(interactive=True), + chatbot_comp: gr.update( + value=webui_manager.bu_chat_history + + [ + { + "role": "assistant", + "content": format_error_message( + e, context="Agent setup", include_traceback=True + ), + } + ] + ), + } + + +# --- Button Click Handlers --- (Need access to webui_manager) + + +async def handle_submit( + webui_manager: WebuiManager, components: dict[gr.components.Component, Any] +): + """Handles clicks on the main 'Submit' button.""" + user_input_comp = webui_manager.get_component_by_id("browser_use_agent.user_input") + user_input_value = components.get(user_input_comp, "").strip() + + # Check if waiting for user assistance + if webui_manager.bu_response_event and not webui_manager.bu_response_event.is_set(): + logger.info(f"User submitted assistance: {user_input_value}") + webui_manager.bu_user_help_response = ( + user_input_value if user_input_value else "User provided no text response." + ) + webui_manager.bu_response_event.set() + # UI updates handled by the main loop reacting to the event being set + yield { + user_input_comp: gr.update( + value="", + interactive=False, + placeholder="Waiting for agent to continue...", + ), + webui_manager.get_component_by_id("browser_use_agent.run_button"): gr.update( + value="⏳ Running...", interactive=False + ), + } + # Check if a task is currently running (using _current_task) + elif webui_manager.bu_current_task and not webui_manager.bu_current_task.done(): + logger.warning( + "Submit button clicked while agent is already running and not asking for help." + ) + gr.Info("Agent is currently running. Please wait or use Stop/Pause.") + yield {} # No change + else: + # Handle submission for a new task + logger.info("Submit button clicked for new task.") + # Use async generator to stream updates from run_agent_task + async for update in run_agent_task(webui_manager, components): + yield update + + +async def handle_stop(webui_manager: WebuiManager): + """Handles clicks on the 'Stop' button.""" + logger.info("Stop button clicked.") + agent = webui_manager.bu_agent + task = webui_manager.bu_current_task + + if agent and task and not task.done(): + # Signal the agent to stop by setting its internal flag + agent.state.stopped = True + agent.state.paused = False # Ensure not paused if stopped + return { + webui_manager.get_component_by_id("browser_use_agent.stop_button"): gr.update( + interactive=False, value="⏹️ Stopping..." + ), + webui_manager.get_component_by_id("browser_use_agent.pause_resume_button"): gr.update( + interactive=False + ), + webui_manager.get_component_by_id("browser_use_agent.run_button"): gr.update( + interactive=False + ), + } + else: + logger.warning("Stop clicked but agent is not running or task is already done.") + # Reset UI just in case it's stuck + return { + webui_manager.get_component_by_id("browser_use_agent.run_button"): gr.update( + interactive=True + ), + webui_manager.get_component_by_id("browser_use_agent.stop_button"): gr.update( + interactive=False + ), + webui_manager.get_component_by_id("browser_use_agent.pause_resume_button"): gr.update( + interactive=False + ), + webui_manager.get_component_by_id("browser_use_agent.clear_button"): gr.update( + interactive=True + ), + } + + +async def handle_pause_resume(webui_manager: WebuiManager): + """Handles clicks on the 'Pause/Resume' button.""" + agent = webui_manager.bu_agent + task = webui_manager.bu_current_task + + if agent and task and not task.done(): + if agent.state.paused: + logger.info("Resume button clicked.") + agent.resume() + # UI update happens in main loop + return { + webui_manager.get_component_by_id( + "browser_use_agent.pause_resume_button" + ): gr.update(value="⏸️ Pause", interactive=True) + } # Optimistic update + else: + logger.info("Pause button clicked.") + agent.pause() + return { + webui_manager.get_component_by_id( + "browser_use_agent.pause_resume_button" + ): gr.update(value="▶️ Resume", interactive=True) + } # Optimistic update + else: + logger.warning("Pause/Resume clicked but agent is not running or doesn't support state.") + return {} # No change + + +async def handle_clear(webui_manager: WebuiManager): + """Handles clicks on the 'Clear' button.""" + logger.info("Clear button clicked.") + + # Stop any running task first + task = webui_manager.bu_current_task + if task and not task.done(): + logger.info("Clearing requires stopping the current task.") + if webui_manager.bu_agent and hasattr(webui_manager.bu_agent, "stop"): + webui_manager.bu_agent.stop() + task.cancel() + try: + await asyncio.wait_for(task, timeout=2.0) # Wait briefly + except (TimeoutError, asyncio.CancelledError): + pass + except Exception as e: + logger.warning(f"Error stopping task on clear: {e}") + webui_manager.bu_current_task = None + + if webui_manager.bu_controller: + await webui_manager.bu_controller.close_mcp_client() + webui_manager.bu_controller = None + webui_manager.bu_agent = None + + # Reset state stored in manager + webui_manager.bu_chat_history = [] + webui_manager.bu_response_event = None + webui_manager.bu_user_help_response = None + webui_manager.bu_agent_task_id = None + + logger.info("Agent state and browser resources cleared.") + + # Reset UI components + return { + webui_manager.get_component_by_id("browser_use_agent.chatbot"): gr.update(value=[]), + webui_manager.get_component_by_id("browser_use_agent.user_input"): gr.update( + value="", placeholder="Enter your task here..." + ), + webui_manager.get_component_by_id("browser_use_agent.agent_history_file"): gr.update( + value=None + ), + webui_manager.get_component_by_id("browser_use_agent.recording_gif"): gr.update(value=None), + webui_manager.get_component_by_id("browser_use_agent.browser_view"): gr.update( + value="
Browser Cleared
" + ), + webui_manager.get_component_by_id("browser_use_agent.run_button"): gr.update( + value="▶️ Submit Task", interactive=True + ), + webui_manager.get_component_by_id("browser_use_agent.stop_button"): gr.update( + interactive=False + ), + webui_manager.get_component_by_id("browser_use_agent.pause_resume_button"): gr.update( + value="⏸️ Pause", interactive=False + ), + webui_manager.get_component_by_id("browser_use_agent.clear_button"): gr.update( + interactive=True + ), + } + + +# --- Tab Creation Function --- + + +def create_browser_use_agent_tab(webui_manager: WebuiManager): + """ + Create the run agent tab, defining UI, state, and handlers. + """ + webui_manager.init_browser_use_agent() + + # --- Define UI Components --- + tab_components = {} + with gr.Column(): + # Add custom CSS and JavaScript for enhanced chat formatting + gr.HTML(f"") + gr.HTML(CHAT_FORMATTING_JS) + + # Progress indicator + progress_text = gr.Markdown("Ready to start", elem_id="progress_text") + + chatbot = gr.Chatbot( + lambda: webui_manager.bu_chat_history, # Load history dynamically + elem_id="browser_use_chatbot", + label="Agent Interaction", + type="messages", + height=600, + show_copy_button=True, + ) + user_input = gr.Textbox( + label="Your Task or Response", + placeholder="Enter your task here or provide assistance when asked.", + lines=3, + interactive=True, + elem_id="user_input", + ) + with gr.Row(): + stop_button = gr.Button("⏹️ Stop", interactive=False, variant="stop", scale=2) + pause_resume_button = gr.Button( + "⏸️ Pause", interactive=False, variant="secondary", scale=2, visible=True + ) + clear_button = gr.Button("🗑️ Clear", interactive=True, variant="secondary", scale=2) + run_button = gr.Button("▶️ Submit Task", variant="primary", scale=3) + + browser_view = gr.HTML( + value="

Browser View (Requires Headless=True)

", + label="Browser Live View", + elem_id="browser_view", + visible=False, + ) + with gr.Column(): + gr.Markdown("### Task Outputs") + agent_history_file = gr.File(label="Agent History JSON", interactive=False) + recording_gif = gr.Image( + label="Task Recording GIF", + format="gif", + interactive=False, + type="filepath", + ) + + # --- Store Components in Manager --- + tab_components.update( + { + "progress_text": progress_text, + "chatbot": chatbot, + "user_input": user_input, + "clear_button": clear_button, + "run_button": run_button, + "stop_button": stop_button, + "pause_resume_button": pause_resume_button, + "agent_history_file": agent_history_file, + "recording_gif": recording_gif, + "browser_view": browser_view, + } + ) + webui_manager.add_components( + "browser_use_agent", tab_components + ) # Use "browser_use_agent" as tab_name prefix + + all_managed_components = set( + webui_manager.get_components() + ) # Get all components known to manager + run_tab_outputs = list(tab_components.values()) + + async def submit_wrapper(*args): + """Wrapper for handle_submit that yields its results.""" + # Convert individual component values to components dict + components_dict = {} + all_components = list(all_managed_components) + for i, comp in enumerate(all_components): + if i < len(args): + components_dict[comp] = args[i] + + async for update in handle_submit(webui_manager, components_dict): + yield update + + async def stop_wrapper(): + """Wrapper for handle_stop.""" + update_dict = await handle_stop(webui_manager) + yield update_dict + + async def pause_resume_wrapper(): + """Wrapper for handle_pause_resume.""" + update_dict = await handle_pause_resume(webui_manager) + yield update_dict + + async def clear_wrapper(): + """Wrapper for handle_clear.""" + update_dict = await handle_clear(webui_manager) + yield update_dict + + # --- Connect Event Handlers using the Wrappers -- + run_button.click( + fn=submit_wrapper, + inputs=list(all_managed_components), + outputs=run_tab_outputs, + trigger_mode="multiple", + ) + user_input.submit( + fn=submit_wrapper, inputs=list(all_managed_components), outputs=run_tab_outputs + ) + stop_button.click(fn=stop_wrapper, inputs=None, outputs=run_tab_outputs) + pause_resume_button.click(fn=pause_resume_wrapper, inputs=None, outputs=run_tab_outputs) + clear_button.click(fn=clear_wrapper, inputs=None, outputs=run_tab_outputs) diff --git a/src/web_ui/webui/components/chat_formatter.py b/src/web_ui/webui/components/chat_formatter.py new file mode 100644 index 00000000..41ca74d6 --- /dev/null +++ b/src/web_ui/webui/components/chat_formatter.py @@ -0,0 +1,611 @@ +""" +Chat message formatting utilities for enhanced display. +""" + +import re +from typing import Any + + +def format_agent_message(content: str, metadata: dict[str, Any] | None = None) -> str: + """ + Format agent messages with rich styling, action badges, and interactive elements. + + Args: + content: The message content + metadata: Optional metadata including action type, status, etc. + + Returns: + HTML-formatted message string + """ + if not content: + return "" + + formatted = content + + # Add action badge if action metadata is present + if metadata and "action" in metadata: + action = metadata["action"].lower() + status = metadata.get("status", "default") + badge_html = create_action_badge(action, status) + formatted = badge_html + " " + formatted + + # Make URLs clickable + formatted = make_urls_clickable(formatted) + + # Format code blocks + formatted = format_code_blocks(formatted) + + # Format inline code + formatted = format_inline_code(formatted) + + # Add collapsible sections for long content + if len(formatted) > 500 and metadata and metadata.get("collapsible"): + formatted = create_collapsible_section("Details", formatted) + + return formatted + + +def create_action_badge(action: str, status: str = "default") -> str: + """ + Create an action badge with appropriate styling. + + Args: + action: The action type (navigate, click, type, extract, etc.) + status: The status (running, completed, error) + + Returns: + HTML badge element + """ + # Map actions to display text and styles + action_map = { + "navigate": {"text": "🧭 Navigate", "class": "navigate"}, + "click": {"text": "🖱️ Click", "class": "click"}, + "type": {"text": "⌨️ Type", "class": "type"}, + "input": {"text": "⌨️ Input", "class": "type"}, + "extract": {"text": "📊 Extract", "class": "extract"}, + "search": {"text": "🔍 Search", "class": "search"}, + "scroll": {"text": "📜 Scroll", "class": "scroll"}, + "wait": {"text": "⏱️ Wait", "class": "wait"}, + "screenshot": {"text": "📸 Screenshot", "class": "screenshot"}, + "done": {"text": "✅ Done", "class": "done"}, + "thinking": {"text": "🤔 Thinking", "class": "thinking"}, + } + + action_info = action_map.get(action, {"text": f"⚡ {action.title()}", "class": "default"}) + status_class = f"status-{status}" if status != "default" else "" + + return f'{action_info["text"]}' + + +def make_urls_clickable(text: str) -> str: + """ + Convert URLs in text to clickable links. + + Args: + text: Text containing URLs + + Returns: + Text with URLs converted to HTML links + """ + url_pattern = r'(https?://[^\s<>"]+|www\.[^\s<>"]+)' + + def replace_url(match): + url = match.group(0) + # Add https:// if only www. is present + full_url = url if url.startswith("http") else f"https://{url}" + # Truncate long URLs for display + display_url = url if len(url) <= 50 else url[:47] + "..." + return f'{display_url}' + + return re.sub(url_pattern, replace_url, text) + + +def format_code_blocks(text: str) -> str: + """ + Format code blocks with proper HTML. + + Args: + text: Text containing code blocks marked with ``` + + Returns: + Text with formatted code blocks + """ + # Match code blocks with optional language + pattern = r"```(\w+)?\n(.*?)```" + + def replace_code_block(match): + language = match.group(1) or "" + code = match.group(2) + lang_class = f' class="language-{language}"' if language else "" + return f"
{code}
" + + return re.sub(pattern, replace_code_block, text, flags=re.DOTALL) + + +def format_inline_code(text: str) -> str: + """ + Format inline code with backticks. + + Args: + text: Text containing inline code marked with ` + + Returns: + Text with formatted inline code + """ + # Match inline code (single backticks not in code blocks) + pattern = r"`([^`\n]+)`" + return re.sub(pattern, r'\1', text) + + +def create_collapsible_section(title: str, content: str, collapsed: bool = True) -> str: + """ + Create a collapsible section for long content. + + Args: + title: Section title + content: Section content + collapsed: Whether to start collapsed + + Returns: + HTML collapsible section + """ + collapsed_class = "collapsed" if collapsed else "" + + return f""" +
+
+ + {title} +
+
+ {content} +
+
+ """ + + +def add_copy_button(content: str, label: str = "Copy") -> str: + """ + Add a copy button to content. + + Args: + content: Content to make copyable + label: Button label + + Returns: + HTML with copy button + """ + import uuid + + content_id = f"copy-content-{uuid.uuid4().hex[:8]}" + + return f""" +
+
{content}
+ +
+ """ + + +def format_error_message( + error: Exception | str, context: str | None = None, include_traceback: bool = False +) -> str: + """ + Format error messages in a user-friendly way. + + Args: + error: The error (Exception object or string) + context: Optional context about where/when the error occurred + include_traceback: Whether to include full traceback (for debugging) + + Returns: + Formatted HTML error message + """ + import traceback + + # Extract error details + if isinstance(error, Exception): + error_type = type(error).__name__ + error_message = str(error) + trace = traceback.format_exc() if include_traceback else None + else: + error_type = "Error" + error_message = str(error) + trace = None + + # Create user-friendly error message + error_icon = "🚫" + error_html = f""" +
+
+ {error_icon} + {error_type} +
+ """ + + if context: + error_html += f""" +
+ Context: {context} +
+ """ + + error_html += f""" +
+ {error_message} +
+ """ + + # Add helpful suggestions based on error type + suggestions = _get_error_suggestions(error_type, error_message) + if suggestions: + error_html += """ +
+ 💡 Suggestions: +
    + """ + for suggestion in suggestions: + error_html += f"
  • {suggestion}
  • " + error_html += """ +
+
+ """ + + # Add collapsible traceback if available + if trace: + trace_html = create_collapsible_section( + "Technical Details (Traceback)", f"
{trace}
", collapsed=True + ) + error_html += trace_html + + error_html += """ +
+ """ + + return error_html + + +def _get_error_suggestions(error_type: str, error_message: str) -> list[str]: + """ + Get helpful suggestions based on error type and message. + + Args: + error_type: Type of error + error_message: Error message text + + Returns: + List of suggestions + """ + suggestions = [] + error_msg_lower = error_message.lower() + + # API Key errors + if ( + "api key" in error_msg_lower + or "authentication" in error_msg_lower + or "unauthorized" in error_msg_lower + ): + suggestions.extend( + [ + "Check that your API key is correctly set in the .env file", + "Verify that the API key has not expired", + "Ensure the API key has the necessary permissions", + ] + ) + + # Connection errors + elif ( + "connection" in error_msg_lower + or "timeout" in error_msg_lower + or "network" in error_msg_lower + ): + suggestions.extend( + [ + "Check your internet connection", + "Verify that the API endpoint is accessible", + "Try increasing the timeout value in settings", + ] + ) + + # Rate limit errors + elif "rate limit" in error_msg_lower or "quota" in error_msg_lower: + suggestions.extend( + [ + "Wait a few moments before trying again", + "Check your API usage quota", + "Consider upgrading your API plan if you're hitting limits frequently", + ] + ) + + # Browser/Playwright errors + elif "browser" in error_msg_lower or "playwright" in error_msg_lower: + suggestions.extend( + [ + "Ensure Playwright browsers are installed: `playwright install chromium --with-deps`", + "Try restarting the browser session using the Clear button", + "Check if the browser path is correct in settings", + ] + ) + + # Model/LLM errors + elif "model" in error_msg_lower and ( + "not found" in error_msg_lower or "does not exist" in error_msg_lower + ): + suggestions.extend( + [ + "Verify that the model name is correct in Agent Settings", + "Check if the model is available for your API plan", + "Try using a different model from the same provider", + ] + ) + + # File/Path errors + elif "filenotfound" in error_type.lower() or "no such file" in error_msg_lower: + suggestions.extend( + [ + "Check that the file path exists and is accessible", + "Verify that you have read/write permissions for the directory", + "Use absolute paths if relative paths are causing issues", + ] + ) + + # Generic fallback + if not suggestions: + suggestions.extend( + [ + "Check the Agent Settings tab for configuration issues", + "Review the technical details below for more information", + "Try restarting the agent with the Clear button", + ] + ) + + return suggestions + + +# CSS for chat formatting +CHAT_FORMATTING_CSS = """ +/* Action Badges */ +.action-badge { + display: inline-block; + padding: 3px 10px; + border-radius: 12px; + font-size: 0.75em; + font-weight: 600; + margin-right: 8px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.action-badge.navigate { background: #FF5722; color: white; } +.action-badge.click { background: #4CAF50; color: white; } +.action-badge.type { background: #2196F3; color: white; } +.action-badge.extract { background: #9C27B0; color: white; } +.action-badge.search { background: #FF9800; color: white; } +.action-badge.scroll { background: #607D8B; color: white; } +.action-badge.wait { background: #9E9E9E; color: white; } +.action-badge.screenshot { background: #00BCD4; color: white; } +.action-badge.done { background: #4CAF50; color: white; } +.action-badge.thinking { background: #673AB7; color: white; } +.action-badge.default { background: #757575; color: white; } + +.action-badge.status-running { animation: pulse 1.5s ease-in-out infinite; } +.action-badge.status-error { background: #F44336 !important; } + +@keyframes pulse { + 0%, 100% { opacity: 0.8; } + 50% { opacity: 1; } +} + +/* URL Links */ +.url-link { + color: #1976D2; + text-decoration: none; + border-bottom: 1px solid #1976D2; + transition: color 0.2s, border-color 0.2s; +} + +.url-link:hover { + color: #0D47A1; + border-bottom-color: #0D47A1; +} + +/* Code Blocks */ +pre { + background: #f5f5f5; + border: 1px solid #e0e0e0; + border-radius: 6px; + padding: 12px 16px; + overflow-x: auto; + margin: 8px 0; +} + +pre code { + font-family: 'Courier New', 'Monaco', monospace; + font-size: 0.9em; + line-height: 1.5; + color: #212121; +} + +.inline-code { + font-family: 'Courier New', 'Monaco', monospace; + font-size: 0.9em; + background: #f5f5f5; + padding: 2px 6px; + border-radius: 3px; + color: #d32f2f; +} + +/* Collapsible Sections */ +.collapsible-section { + border: 1px solid #e0e0e0; + border-radius: 6px; + margin: 8px 0; + overflow: hidden; +} + +.collapsible-header { + background: #f5f5f5; + padding: 10px 14px; + cursor: pointer; + user-select: none; + display: flex; + align-items: center; + gap: 8px; + transition: background 0.2s; +} + +.collapsible-header:hover { + background: #eeeeee; +} + +.collapse-icon { + transition: transform 0.2s ease; + font-size: 0.8em; +} + +.collapsible-section:not(.collapsed) .collapse-icon { + transform: rotate(90deg); +} + +.collapsible-title { + font-weight: 500; +} + +.collapsible-content { + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease; + padding: 0 14px; +} + +.collapsible-section:not(.collapsed) .collapsible-content { + max-height: 1000px; + padding: 14px; + overflow-y: auto; +} + +/* Copy Container */ +.copy-container { + position: relative; + background: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 6px; + padding: 12px; + margin: 8px 0; +} + +.copy-button { + position: absolute; + top: 8px; + right: 8px; + padding: 6px 12px; + background: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 0.85em; + transition: background 0.2s; +} + +.copy-button:hover { + background: #0056b3; +} + +.copy-button.copied { + background: #28a745; +} + +.copy-content { + font-family: 'Courier New', 'Monaco', monospace; + white-space: pre-wrap; + word-break: break-word; + padding-right: 80px; +} + +/* Error Container */ +.error-container { + background: #fff3f3; + border: 2px solid #f44336; + border-radius: 8px; + padding: 16px; + margin: 12px 0; +} + +.error-header { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 12px; + font-size: 1.1em; +} + +.error-icon { + font-size: 1.5em; +} + +.error-title { + font-weight: 700; + color: #d32f2f; +} + +.error-context { + background: #ffebee; + border-left: 3px solid #f44336; + padding: 8px 12px; + margin-bottom: 10px; + border-radius: 4px; +} + +.error-message { + font-size: 1em; + line-height: 1.5; + margin: 10px 0; + color: #333; +} + +.error-suggestions { + background: #e8f5e9; + border-left: 3px solid #4caf50; + padding: 12px 16px; + margin-top: 12px; + border-radius: 4px; +} + +.error-suggestions ul { + margin: 8px 0 0 0; + padding-left: 20px; +} + +.error-suggestions li { + margin: 6px 0; + color: #2e7d32; +} +""" + +# JavaScript for copy functionality +CHAT_FORMATTING_JS = """ + +""" diff --git a/src/web_ui/webui/components/dashboard_main.py b/src/web_ui/webui/components/dashboard_main.py new file mode 100644 index 00000000..b89a1277 --- /dev/null +++ b/src/web_ui/webui/components/dashboard_main.py @@ -0,0 +1,238 @@ +""" +Dashboard Main Content Area + +Unified execution area for Browser Use Agent and Deep Research Agent. +Includes agent selector, task input, control buttons, and output display. +""" + +import logging + +import gradio as gr + +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + + +def create_dashboard_main(webui_manager: WebuiManager): + """ + Create the main dashboard content area with agent selector and unified execution. + + Args: + webui_manager: WebUI manager instance + """ + main_components = {} + + with gr.Column(elem_classes=["dashboard-main"]): + # Agent Selector + gr.Markdown("### 🤖 Agent Selection") + agent_selector = gr.Dropdown( + choices=["Browser Use Agent", "Deep Research Agent"], + value="Browser Use Agent", + label="Select Agent", + info="Choose which agent to use for this task", + interactive=True, + elem_classes=["agent-selector"], + ) + + gr.Markdown("---") + + # Browser Use Agent Section + with gr.Group(visible=True) as browser_use_group: + gr.Markdown("### 🌐 Browser Use Agent") + gr.Markdown("_Control a web browser to perform tasks automatically_") + + # Import browser use agent tab components + # We'll reuse the existing function but adapt it + with gr.Column(): + # Task Input + user_input = gr.Textbox( + label="Task Description", + placeholder="Enter what you want the browser agent to do...", + lines=3, + interactive=True, + ) + + # Control Buttons + with gr.Row(): + run_button = gr.Button("▶️ Run Agent", variant="primary", scale=2) + stop_button = gr.Button("⏹️ Stop", variant="stop", scale=1, interactive=False) + pause_resume_button = gr.Button( + "⏸️ Pause", variant="secondary", scale=1, interactive=False + ) + clear_button = gr.Button("🗑️ Clear", variant="secondary", scale=1) + + # Progress Display + progress_text = gr.Markdown("**Status:** Ready", elem_classes=["progress-text"]) + + # Output Area + with gr.Tabs(): + with gr.TabItem("💬 Chat"): + chatbot = gr.Chatbot( + label="Agent Conversation", + type="messages", + height=500, + show_copy_button=True, + elem_classes=["agent-chatbot"], + ) + + # User assistance input (for ask_for_assistant callbacks) + with gr.Row(visible=False) as user_help_row: + user_help_input = gr.Textbox( + label="Assistant Response", + placeholder="Provide information or confirmation...", + lines=2, + ) + submit_help_button = gr.Button("Submit Response", variant="primary") + + with gr.TabItem("🖼️ Browser View"): + browser_view = gr.Image( + label="Current Browser View", + type="filepath", + interactive=False, + height=500, + ) + + with gr.TabItem("📹 Recording"): + recording_gif = gr.File( + label="Session Recording (GIF)", + interactive=False, + ) + + with gr.TabItem("📜 History"): + agent_history_file = gr.File( + label="Agent History (JSON)", + interactive=False, + ) + + # Deep Research Agent Section + with gr.Group(visible=False) as deep_research_group: + gr.Markdown("### 🔬 Deep Research Agent") + gr.Markdown("_Perform comprehensive multi-source research with automatic synthesis_") + + with gr.Column(): + # Research Task Input + research_task = gr.Textbox( + label="Research Topic", + placeholder="Enter the topic you want to research...", + lines=3, + interactive=True, + ) + + # Research-Specific Options + with gr.Row(): + resume_task_id = gr.Textbox( + label="Resume Task ID (Optional)", + placeholder="Leave empty for new research", + interactive=True, + ) + parallel_num = gr.Slider( + minimum=1, + maximum=5, + value=1, + step=1, + label="Parallel Agents", + info="Number of concurrent browser agents", + interactive=True, + ) + + max_query = gr.Textbox( + label="Save Directory", + value="./tmp/deep_research", + interactive=True, + info="Directory to save research outputs", + ) + + # Control Buttons + with gr.Row(): + start_button = gr.Button("▶️ Start Research", variant="primary", scale=2) + stop_button_dr = gr.Button("⏹️ Stop", variant="stop", scale=1, interactive=False) + clear_button_dr = gr.Button("🗑️ Clear", variant="secondary", scale=1) + + # Research Output + with gr.Tabs(): + with gr.TabItem("📄 Report"): + markdown_display = gr.Markdown( + "Research results will appear here...", + elem_classes=["research-report"], + ) + + with gr.TabItem("⬇️ Download"): + markdown_download = gr.File( + label="Download Research Report", + interactive=False, + ) + + # MCP Server Config (hidden, used internally) + mcp_server_config = gr.Textbox(visible=False) + + # Register Browser Use Agent components with old-style IDs for compatibility + browser_use_components = { + "user_input": user_input, + "run_button": run_button, + "stop_button": stop_button, + "pause_resume_button": pause_resume_button, + "clear_button": clear_button, + "progress_text": progress_text, + "chatbot": chatbot, + "user_help_row": user_help_row, + "user_help_input": user_help_input, + "submit_help_button": submit_help_button, + "browser_view": browser_view, + "recording_gif": recording_gif, + "agent_history_file": agent_history_file, + } + webui_manager.add_components("browser_use_agent", browser_use_components) + + # Register Deep Research Agent components with old-style IDs for compatibility + deep_research_components = { + "research_task": research_task, + "resume_task_id": resume_task_id, + "parallel_num": parallel_num, + "max_query": max_query, + "start_button": start_button, + "stop_button": stop_button_dr, + "clear_button": clear_button_dr, + "markdown_display": markdown_display, + "markdown_download": markdown_download, + "mcp_server_config": mcp_server_config, + } + webui_manager.add_components("deep_research_agent", deep_research_components) + + # Register dashboard-level components + main_components.update( + { + "agent_selector": agent_selector, + "browser_use_group": browser_use_group, + "deep_research_group": deep_research_group, + } + ) + webui_manager.add_components("dashboard_main", main_components) + + # Agent selector change handler + def switch_agent(agent_type: str): + """Toggle visibility of agent-specific UI sections.""" + show_browser_use = agent_type == "Browser Use Agent" + show_deep_research = agent_type == "Deep Research Agent" + + # Update webui_manager state + if hasattr(webui_manager, "current_agent_type"): + webui_manager.current_agent_type = ( + "browser_use" if show_browser_use else "deep_research" + ) + + return { + browser_use_group: gr.update(visible=show_browser_use), + deep_research_group: gr.update(visible=show_deep_research), + } + + agent_selector.change( + fn=switch_agent, + inputs=[agent_selector], + outputs=[browser_use_group, deep_research_group], + ) + + # Note: Actual agent execution handlers (run_button.click, start_button.click, etc.) + # will be wired up in interface.py after all components are registered + + return main_components diff --git a/src/web_ui/webui/components/dashboard_settings.py b/src/web_ui/webui/components/dashboard_settings.py new file mode 100644 index 00000000..7ccf8218 --- /dev/null +++ b/src/web_ui/webui/components/dashboard_settings.py @@ -0,0 +1,547 @@ +""" +Dashboard Settings Panel + +Consolidated settings panel with LLM, Browser, MCP, and Advanced configuration. +Collapsible by default with toggle button. +""" + +import logging +import os + +import gradio as gr + +from src.web_ui.utils import config +from src.web_ui.utils.mcp_config import get_mcp_config_path, get_mcp_config_summary, load_mcp_config +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + + +def strtobool(val): + """Convert a string representation of truth to true (1) or false (0).""" + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return 1 + elif val in ("n", "no", "f", "false", "off", "0"): + return 0 + else: + raise ValueError(f"invalid truth value {val!r}") + + +def update_model_dropdown(llm_provider): + """Update the model name dropdown with predefined models for the selected provider.""" + print(f"[DEBUG] update_model_dropdown called with provider: {llm_provider}") + logger.info(f"Updating model dropdown for provider: {llm_provider}") + logger.info(f"Available providers: {list(config.model_names.keys())}") + if llm_provider in config.model_names: + models = config.model_names[llm_provider] + print(f"[DEBUG] Found {len(models)} models for {llm_provider}: {models}") + logger.info(f"Found {len(models)} models for {llm_provider}: {models[:3]}...") + result = gr.update( + choices=models, + value=models[0] if models else "", + interactive=True, + ) + print(f"[DEBUG] Returning gr.update with choices: {result.get('choices', 'N/A')}") + return result + else: + print(f"[DEBUG] Provider {llm_provider} not found!") + logger.warning(f"Provider {llm_provider} not found in config.model_names") + return gr.update(choices=[], value="", interactive=True) + + +async def close_browser(webui_manager: WebuiManager): + """Close browser when browser config changes.""" + if webui_manager.bu_current_task and not webui_manager.bu_current_task.done(): + webui_manager.bu_current_task.cancel() + webui_manager.bu_current_task = None + + if webui_manager.bu_browser_context: + logger.info("⚠️ Closing browser context when changing browser config.") + await webui_manager.bu_browser_context.close() + webui_manager.bu_browser_context = None + + if webui_manager.bu_browser: + logger.info("⚠️ Closing browser when changing browser config.") + await webui_manager.bu_browser.close() + webui_manager.bu_browser = None + + +def create_dashboard_settings(webui_manager: WebuiManager): + """ + Create the collapsible settings panel with consolidated configuration. + + Args: + webui_manager: WebUI manager instance + """ + settings_components = {} + + gr.Markdown("## ⚙️ Settings") + + # Save/Load Config at Top + with gr.Row(): + save_config_button = gr.Button("💾 Save", variant="primary", scale=1, size="sm") + save_default_button = gr.Button("⭐ Save as Default", variant="primary", scale=1, size="sm") + load_config_button = gr.Button("📂 Load", variant="secondary", scale=1, size="sm") + + config_file = gr.File( + label="Configuration File", + file_types=[".json"], + interactive=True, + visible=False, + ) + config_status = gr.Textbox(label="Status", lines=1, interactive=False, visible=False) + + gr.Markdown("---") + + # 🤖 LLM Configuration + with gr.Accordion("🤖 LLM Configuration", open=True): + gr.Markdown("**Primary language model** for agent reasoning") + + with gr.Row(): + llm_provider = gr.Dropdown( + choices=[provider for provider, model in config.model_names.items()], + label="Provider", + value=os.getenv("DEFAULT_LLM", "openai"), + interactive=True, + ) + default_provider = os.getenv("DEFAULT_LLM", "openai") + default_models = config.model_names.get(default_provider, []) + llm_model_name = gr.Dropdown( + label="Model", + choices=default_models, + value=default_models[0] if default_models else "", + interactive=True, + allow_custom_value=True, + ) + + with gr.Row(): + llm_temperature = gr.Slider( + minimum=0.0, + maximum=2.0, + value=0.6, + step=0.1, + label="Temperature", + info="0=deterministic, 2=creative", + interactive=True, + ) + use_vision = gr.Checkbox( + label="Enable Vision", + value=True, + info="Use screenshots for context", + interactive=True, + ) + + # API Credentials (collapsed) + with gr.Accordion("🔑 API Credentials", open=False): + with gr.Row(): + llm_base_url = gr.Textbox( + label="Base URL", + value="", + placeholder="https://api.example.com/v1", + info="Leave blank for default", + ) + llm_api_key = gr.Textbox( + label="API Key", + type="password", + value="", + placeholder="sk-...", + info="Leave blank to use .env", + ) + + # Ollama-specific setting + ollama_num_ctx = gr.Slider( + minimum=2**8, + maximum=2**16, + value=16000, + step=1, + label="Ollama Context Length", + visible=False, + interactive=True, + ) + + # Planner LLM (collapsed by default) + use_planner = gr.Checkbox( + label="Use separate planner model", + value=False, + info="Enable for complex multi-step reasoning", + interactive=True, + ) + + with gr.Group(visible=False) as planner_group: + gr.Markdown("**Planner Model Configuration**") + + with gr.Row(): + planner_llm_provider = gr.Dropdown( + choices=[provider for provider, model in config.model_names.items()], + label="Planner Provider", + value=None, + interactive=True, + ) + planner_llm_model_name = gr.Dropdown( + label="Planner Model", + interactive=True, + allow_custom_value=True, + ) + + planner_llm_temperature = gr.Slider( + minimum=0.0, + maximum=2.0, + value=0.6, + step=0.1, + label="Temperature", + interactive=True, + ) + + planner_use_vision = gr.Checkbox( + label="Enable Vision", + value=False, + interactive=True, + ) + + planner_ollama_num_ctx = gr.Slider( + minimum=2**8, + maximum=2**16, + value=16000, + step=1, + label="Ollama Context", + visible=False, + interactive=True, + ) + + with gr.Accordion("🔑 Planner API Credentials", open=False): + with gr.Row(): + planner_llm_base_url = gr.Textbox( + label="Base URL", + value="", + placeholder="https://api.example.com/v1", + ) + planner_llm_api_key = gr.Textbox( + label="API Key", + type="password", + value="", + placeholder="sk-...", + ) + + # 🌐 Browser Configuration + with gr.Accordion("🌐 Browser Configuration", open=False): + gr.Markdown("**Browser behavior and connection settings**") + + # Custom Browser + use_own_browser = gr.Checkbox( + label="Use Own Browser", + value=bool(strtobool(os.getenv("USE_OWN_BROWSER", "false"))), + info="Connect to your Chrome instance", + interactive=True, + ) + + with gr.Group(visible=False) as custom_browser_group: + browser_binary_path = gr.Textbox( + label="Browser Binary Path", + placeholder="C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", + info="Path to Chrome/Chromium", + ) + browser_user_data_dir = gr.Textbox( + label="User Data Directory", + placeholder="Leave empty for default profile", + info="Custom profile directory", + ) + + # Browser Behavior + gr.Markdown("**Behavior Settings**") + with gr.Row(): + headless = gr.Checkbox( + label="Headless Mode", + value=False, + info="Run without visible GUI", + interactive=True, + ) + keep_browser_open = gr.Checkbox( + label="Keep Open", + value=bool(strtobool(os.getenv("KEEP_BROWSER_OPEN", "true"))), + info="Persist between tasks", + interactive=True, + ) + disable_security = gr.Checkbox( + label="Disable Security", + value=False, + info="⚠️ Use with caution", + interactive=True, + ) + + with gr.Row(): + window_w = gr.Number( + label="Window Width", + value=1280, + interactive=True, + ) + window_h = gr.Number( + label="Window Height", + value=1100, + interactive=True, + ) + + # Advanced Browser Settings (collapsed) + with gr.Accordion("🔗 Advanced Browser Settings", open=False): + gr.Markdown("**Remote debugging and storage paths**") + + with gr.Row(): + cdp_url = gr.Textbox( + label="CDP URL", + value=os.getenv("BROWSER_CDP", None), + placeholder="http://localhost:9222", + ) + wss_url = gr.Textbox( + label="WSS URL", + placeholder="wss://localhost:9222/devtools/browser/...", + ) + + gr.Markdown("**Storage Paths**") + with gr.Row(): + save_recording_path = gr.Textbox( + label="📹 Recording Path", + placeholder="./tmp/record_videos", + ) + save_trace_path = gr.Textbox( + label="📊 Trace Path", + placeholder="./tmp/traces", + ) + + with gr.Row(): + save_agent_history_path = gr.Textbox( + label="📜 History Path", + value="./tmp/agent_history", + ) + save_download_path = gr.Textbox( + label="⬇️ Downloads Path", + value="./tmp/downloads", + ) + + # 🔌 MCP Servers + with gr.Accordion("🔌 MCP Servers", open=False): + gr.Markdown("**Model Context Protocol server configuration**") + + # MCP Status Display + mcp_config_path = get_mcp_config_path() + mcp_config = load_mcp_config() + + if mcp_config and "mcpServers" in mcp_config: + summary = get_mcp_config_summary(mcp_config) + mcp_status_md = f""" +✅ **MCP Configuration Active** + +{summary} + +Configuration file: `{mcp_config_path}` +""" + else: + mcp_status_md = f""" +ℹ️ **No MCP Configuration** + +No MCP servers configured. You can add servers via the MCP Settings editor. + +Expected file: `{mcp_config_path}` +""" + + mcp_status_display = gr.Markdown(mcp_status_md) + + # Button to open MCP settings (will be handled in interface.py) + edit_mcp_button = gr.Button( + "📝 Edit MCP Configuration", + variant="secondary", + size="sm", + ) + + # Hidden MCP file upload (for compatibility with old agent_settings) + mcp_json_file = gr.File( + label="Upload MCP Config (Temporary)", + interactive=True, + file_types=[".json"], + visible=False, + ) + mcp_server_config = gr.Textbox( + label="MCP Configuration", + lines=6, + interactive=True, + visible=False, + ) + + # ⚡ Advanced Settings + with gr.Accordion("⚡ Advanced Settings", open=False): + gr.Markdown("**System prompts and agent parameters**") + + # System Prompts + with gr.Accordion("📝 System Prompts", open=False): + override_system_prompt = gr.Textbox( + label="Override System Prompt", + lines=4, + placeholder="Replace the entire system prompt...", + interactive=True, + ) + extend_system_prompt = gr.Textbox( + label="Extend System Prompt", + lines=4, + placeholder="Add additional instructions...", + interactive=True, + ) + + # Agent Parameters + gr.Markdown("**Agent Limits**") + with gr.Row(): + max_steps = gr.Slider( + minimum=1, + maximum=1000, + value=100, + step=1, + label="Max Steps", + interactive=True, + ) + max_actions = gr.Slider( + minimum=1, + maximum=100, + value=10, + step=1, + label="Max Actions/Step", + interactive=True, + ) + + with gr.Row(): + max_input_tokens = gr.Number( + label="Max Input Tokens", + value=128000, + precision=0, + interactive=True, + ) + tool_calling_method = gr.Dropdown( + label="Tool Calling Method", + value="auto", + choices=["function_calling", "json_mode", "raw", "auto", "tools", "None"], + interactive=True, + allow_custom_value=True, + ) + + gr.Markdown("---") + + # Save/Load Config at Bottom (repeated for convenience) + with gr.Row(): + save_config_button_bottom = gr.Button("💾 Save Configuration", variant="primary") + load_config_button_bottom = gr.Button("📂 Load Configuration", variant="secondary") + + # NOTE: agent_settings registration removed to avoid duplicate component registrations + # All components are now registered under dashboard_settings namespace only + # Browser use agent tab will read from dashboard_settings namespace + + # Register agent settings components for backward compatibility ONLY + # These are registered under dashboard_settings namespace below + agent_settings_components = { + "override_system_prompt": override_system_prompt, + "extend_system_prompt": extend_system_prompt, + "llm_provider": llm_provider, + "llm_model_name": llm_model_name, + "llm_temperature": llm_temperature, + "use_vision": use_vision, + "llm_base_url": llm_base_url, + "llm_api_key": llm_api_key, + "ollama_num_ctx": ollama_num_ctx, + "planner_llm_provider": planner_llm_provider, + "planner_llm_model_name": planner_llm_model_name, + "planner_llm_temperature": planner_llm_temperature, + "planner_use_vision": planner_use_vision, + "planner_ollama_num_ctx": planner_ollama_num_ctx, + "planner_llm_base_url": planner_llm_base_url, + "planner_llm_api_key": planner_llm_api_key, + "max_steps": max_steps, + "max_actions": max_actions, + "max_input_tokens": max_input_tokens, + "tool_calling_method": tool_calling_method, + "mcp_json_file": mcp_json_file, + "mcp_server_config": mcp_server_config, + } + # Duplicate registration removed - only register under dashboard_settings + # webui_manager.add_components("agent_settings", agent_settings_components) + + # Register browser settings components with old-style IDs for compatibility + browser_settings_components = { + "browser_binary_path": browser_binary_path, + "browser_user_data_dir": browser_user_data_dir, + "use_own_browser": use_own_browser, + "keep_browser_open": keep_browser_open, + "headless": headless, + "disable_security": disable_security, + "window_w": window_w, + "window_h": window_h, + "cdp_url": cdp_url, + "wss_url": wss_url, + "save_recording_path": save_recording_path, + "save_trace_path": save_trace_path, + "save_agent_history_path": save_agent_history_path, + "save_download_path": save_download_path, + } + webui_manager.add_components("browser_settings", browser_settings_components) + + # Register dashboard settings components + settings_components.update( + { + "save_config_button": save_config_button, + "save_default_button": save_default_button, + "load_config_button": load_config_button, + "config_file": config_file, + "config_status": config_status, + "llm_provider": llm_provider, + "llm_model_name": llm_model_name, + "llm_temperature": llm_temperature, + "use_vision": use_vision, + "llm_base_url": llm_base_url, + "llm_api_key": llm_api_key, + "ollama_num_ctx": ollama_num_ctx, + "use_planner": use_planner, + "planner_group": planner_group, + "planner_llm_provider": planner_llm_provider, + "planner_llm_model_name": planner_llm_model_name, + "planner_llm_temperature": planner_llm_temperature, + "planner_use_vision": planner_use_vision, + "planner_ollama_num_ctx": planner_ollama_num_ctx, + "planner_llm_base_url": planner_llm_base_url, + "planner_llm_api_key": planner_llm_api_key, + "use_own_browser": use_own_browser, + "custom_browser_group": custom_browser_group, + "browser_binary_path": browser_binary_path, + "browser_user_data_dir": browser_user_data_dir, + "headless": headless, + "keep_browser_open": keep_browser_open, + "disable_security": disable_security, + "window_w": window_w, + "window_h": window_h, + "cdp_url": cdp_url, + "wss_url": wss_url, + "save_recording_path": save_recording_path, + "save_trace_path": save_trace_path, + "save_agent_history_path": save_agent_history_path, + "save_download_path": save_download_path, + "mcp_status_display": mcp_status_display, + "edit_mcp_button": edit_mcp_button, + "mcp_json_file": mcp_json_file, + "mcp_server_config": mcp_server_config, + "override_system_prompt": override_system_prompt, + "extend_system_prompt": extend_system_prompt, + "max_steps": max_steps, + "max_actions": max_actions, + "max_input_tokens": max_input_tokens, + "tool_calling_method": tool_calling_method, + "save_config_button_bottom": save_config_button_bottom, + "load_config_button_bottom": load_config_button_bottom, + } + ) + + webui_manager.add_components("dashboard_settings", settings_components) + + # NOTE: Event handlers are now wired up in interface.py AFTER all components are registered + # This prevents race conditions and ensures Gradio's event system initializes properly + + # Export component references for event handler wiring in interface.py + # (Components are already accessible via webui_manager.get_component_by_id) + + # Note: Save/Load config handlers will be wired up in interface.py + # to ensure all components are registered first + + return settings_components diff --git a/src/web_ui/webui/components/dashboard_sidebar.py b/src/web_ui/webui/components/dashboard_sidebar.py new file mode 100644 index 00000000..83e0826b --- /dev/null +++ b/src/web_ui/webui/components/dashboard_sidebar.py @@ -0,0 +1,359 @@ +""" +Dashboard Sidebar Component + +Provides status display, quick presets, task history, browser status, and token usage. +""" + +import logging +import os + +import gradio as gr + +from src.web_ui.utils.mcp_config import get_mcp_config_path, load_mcp_config +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + +# Preset configurations +PRESETS = { + "research": { + "name": "🔬 Research Mode", + "config": { + "llm_provider": "anthropic", + "llm_model_name": "claude-3-5-sonnet-20241022", + "llm_temperature": 0.7, + "use_vision": True, + "max_steps": 150, + "max_actions": 10, + "headless": False, + "keep_browser_open": True, + }, + }, + "automation": { + "name": "🤖 Automation Mode", + "config": { + "llm_provider": "openai", + "llm_model_name": "gpt-4o", + "llm_temperature": 0.6, + "use_vision": True, + "max_steps": 100, + "max_actions": 10, + "headless": False, + "keep_browser_open": True, + }, + }, + "custom_browser": { + "name": "🌐 Custom Browser", + "config": { + "llm_provider": "openai", + "llm_model_name": "gpt-4o-mini", + "llm_temperature": 0.6, + "use_vision": True, + "max_steps": 100, + "max_actions": 10, + "use_own_browser": True, + "keep_browser_open": True, + "headless": False, + }, + }, +} + + +def get_status_summary(webui_manager: WebuiManager) -> dict: + """ + Get current status of LLM, Browser, and MCP configuration. + + Returns: + dict with status information + """ + status = { + "llm": {"configured": False, "provider": None, "status_text": "⚠️ Not configured"}, + "browser": {"open": False, "status_text": "🔴 Closed"}, + "mcp": {"configured": False, "count": 0, "status_text": "ℹ️ Not configured"}, + "tokens": {"used": 0, "cost": 0.0}, + } + + # Check LLM configuration + try: + default_llm = os.getenv("DEFAULT_LLM", "openai") + api_key_var = f"{default_llm.upper()}_API_KEY" + api_key_set = bool(os.getenv(api_key_var)) + + if api_key_set: + status["llm"]["configured"] = True + status["llm"]["provider"] = default_llm.title() + status["llm"]["status_text"] = f"✅ {default_llm.title()}" + else: + status["llm"]["status_text"] = f"⚠️ {default_llm.title()} (no API key)" + except Exception as e: + logger.error(f"Error checking LLM status: {e}") + + # Check Browser status + try: + if hasattr(webui_manager, "bu_browser") and webui_manager.bu_browser: + status["browser"]["open"] = True + status["browser"]["status_text"] = "🟢 Open" + else: + status["browser"]["status_text"] = "🔴 Closed" + except Exception as e: + logger.error(f"Error checking browser status: {e}") + + # Check MCP configuration + try: + get_mcp_config_path() # Check if config exists + mcp_config = load_mcp_config() + if mcp_config and "mcpServers" in mcp_config: + mcp_count = len(mcp_config["mcpServers"]) + status["mcp"]["configured"] = True + status["mcp"]["count"] = mcp_count + status["mcp"]["status_text"] = f"✅ {mcp_count} server(s)" + else: + status["mcp"]["status_text"] = "ℹ️ Not configured" + except Exception as e: + logger.error(f"Error checking MCP status: {e}") + + # Get token usage if available + if hasattr(webui_manager, "token_usage") and webui_manager.token_usage: + status["tokens"] = webui_manager.token_usage + + return status + + +def format_status_card(webui_manager: WebuiManager) -> str: + """Format status information as HTML card.""" + status = get_status_summary(webui_manager) + + html = """ +
+

📊 Status

+
+
+ LLM: + {llm_status} +
+
+ Browser: + {browser_status} +
+
+ MCP: + {mcp_status} +
+
+
+ """.format( + llm_status=status["llm"]["status_text"], + browser_status=status["browser"]["status_text"], + mcp_status=status["mcp"]["status_text"], + ) + + return html + + +def format_history_list(webui_manager: WebuiManager) -> str: + """Format recent task history as HTML.""" + if not hasattr(webui_manager, "recent_tasks") or not webui_manager.recent_tasks: + return """ +
+

📜 Recent Tasks

+

No recent tasks

+
+ """ + + items = [] + for task in webui_manager.recent_tasks[-5:]: # Last 5 tasks + task_text = task.get("task", "Unknown task") + timestamp = task.get("timestamp", "") + status_icon = "✅" if task.get("success", False) else "❌" + + # Truncate long task descriptions + if len(task_text) > 50: + task_text = task_text[:47] + "..." + + items.append( + f""" +
+ {status_icon} {timestamp}
+ {task_text} +
+ """ + ) + + html = f""" +
+

📜 Recent Tasks

+
+ {"".join(reversed(items))} +
+
+ """ + + return html + + +def format_token_usage(webui_manager: WebuiManager) -> str: + """Format token usage information as HTML.""" + if not hasattr(webui_manager, "token_usage") or not webui_manager.token_usage: + return "" + + tokens = webui_manager.token_usage + used = tokens.get("used", 0) + cost = tokens.get("cost", 0.0) + + html = f""" +
+

💰 Usage

+
+
+ Tokens: {used:,} +
+
+ Est. Cost: ${cost:.4f} +
+
+
+ """ + + return html + + +def load_preset_config(preset_name: str, webui_manager: WebuiManager): + """ + Load a preset configuration and return component updates. + + Args: + preset_name: Name of the preset to load + webui_manager: WebUI manager instance + + Returns: + List of gr.update() objects for each component + """ + if preset_name not in PRESETS: + logger.warning(f"Unknown preset: {preset_name}") + return [] + + preset = PRESETS[preset_name] + preset_config = preset["config"] + + # Map preset values to component IDs and create updates + updates = [] + + # Get all components that need updating + component_mapping = { + "llm_provider": "dashboard_settings.llm_provider", + "llm_model_name": "dashboard_settings.llm_model_name", + "llm_temperature": "dashboard_settings.llm_temperature", + "use_vision": "dashboard_settings.use_vision", + "max_steps": "dashboard_settings.max_steps", + "max_actions": "dashboard_settings.max_actions", + "headless": "dashboard_settings.headless", + "keep_browser_open": "dashboard_settings.keep_browser_open", + "use_own_browser": "dashboard_settings.use_own_browser", + } + + for config_key, component_id in component_mapping.items(): + if config_key in preset_config: + try: + component = webui_manager.get_component_by_id(component_id) + if isinstance(preset_config, dict): + updates.append((component, preset_config[config_key])) + except KeyError: + logger.debug(f"Component not found: {component_id}") + continue + + return updates + + +def create_dashboard_sidebar(webui_manager: WebuiManager): + """ + Create the dashboard sidebar with status, presets, and history. + + Args: + webui_manager: WebUI manager instance + """ + sidebar_components = {} + + with gr.Column(elem_classes=["dashboard-sidebar"]): + # Status Card + status_display = gr.HTML( + value=format_status_card(webui_manager), + elem_classes=["status-display"], + ) + + refresh_status_btn = gr.Button( + "🔄 Refresh", + size="sm", + variant="secondary", + scale=1, + ) + + # Preset Buttons + gr.HTML("

🎯 Quick Presets

") + + with gr.Column(elem_classes=["preset-button-group"]): + research_btn = gr.Button( + "🔬 Research Mode", + variant="secondary", + size="lg", + elem_classes=["preset-button"], + ) + automation_btn = gr.Button( + "🤖 Automation Mode", + variant="secondary", + size="lg", + elem_classes=["preset-button"], + ) + custom_browser_btn = gr.Button( + "🌐 Custom Browser", + variant="secondary", + size="lg", + elem_classes=["preset-button"], + ) + + # Task History + history_display = gr.HTML( + value=format_history_list(webui_manager), + elem_classes=["history-display"], + ) + + # Token Usage (optional, only shown if data available) + token_display = gr.HTML( + value=format_token_usage(webui_manager), + visible=bool(hasattr(webui_manager, "token_usage") and webui_manager.token_usage), + elem_classes=["token-display"], + ) + + # Register components + sidebar_components.update( + { + "status_display": status_display, + "refresh_status_btn": refresh_status_btn, + "research_btn": research_btn, + "automation_btn": automation_btn, + "custom_browser_btn": custom_browser_btn, + "history_display": history_display, + "token_display": token_display, + } + ) + + webui_manager.add_components("dashboard_sidebar", sidebar_components) + + # Wire up refresh button + def refresh_status(): + """Refresh all status displays.""" + return [ + gr.update(value=format_status_card(webui_manager)), + gr.update(value=format_history_list(webui_manager)), + gr.update(value=format_token_usage(webui_manager)), + ] + + refresh_status_btn.click( + fn=refresh_status, + inputs=[], + outputs=[status_display, history_display, token_display], + ) + + # Note: Preset button handlers will be wired up after dashboard_settings is created + # This is done in interface.py to ensure settings components exist first + + return sidebar_components diff --git a/src/web_ui/webui/components/deep_research_agent_tab.py b/src/web_ui/webui/components/deep_research_agent_tab.py new file mode 100644 index 00000000..c425f206 --- /dev/null +++ b/src/web_ui/webui/components/deep_research_agent_tab.py @@ -0,0 +1,509 @@ +import asyncio +import json +import logging +import os +from collections.abc import AsyncGenerator +from typing import Any + +import gradio as gr +from gradio.components import Component + +from src.web_ui.agent.deep_research.deep_research_agent import DeepResearchAgent +from src.web_ui.utils import llm_provider +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + + +async def _initialize_llm( + provider: str | None, + model_name: str | None, + temperature: float, + base_url: str | None, + api_key: str | None, + num_ctx: int | None = None, +): + """Initializes the LLM based on settings. Returns None if provider/model is missing.""" + if not provider or not model_name: + logger.info("LLM Provider or Model Name not specified, LLM will be None.") + return None + try: + logger.info( + f"Initializing LLM: Provider={provider}, Model={model_name}, Temp={temperature}" + ) + # Use your actual LLM provider logic here + llm = llm_provider.get_llm_model( + provider=provider, + model_name=model_name, + temperature=temperature, + base_url=base_url or None, + api_key=api_key or None, + num_ctx=num_ctx if provider == "ollama" else None, + ) + return llm + except Exception as e: + logger.error(f"Failed to initialize LLM: {e}", exc_info=True) + gr.Warning( + f"Failed to initialize LLM '{model_name}' for provider '{provider}'. Please check settings. Error: {e}" + ) + return None + + +def _read_file_safe(file_path: str) -> str | None: + """Safely read a file, returning None if it doesn't exist or on error.""" + if not os.path.exists(file_path): + return None + try: + with open(file_path, encoding="utf-8") as f: + return f.read() + except Exception as e: + logger.error(f"Error reading file {file_path}: {e}") + return None + + +# --- Deep Research Agent Specific Logic --- + + +async def run_deep_research( + webui_manager: WebuiManager, components: dict[Component, Any] +) -> AsyncGenerator[dict[Component, Any]]: + """Handles initializing and running the DeepResearchAgent.""" + + # --- Get Components --- + research_task_comp = webui_manager.get_component_by_id("deep_research_agent.research_task") + resume_task_id_comp = webui_manager.get_component_by_id("deep_research_agent.resume_task_id") + parallel_num_comp = webui_manager.get_component_by_id("deep_research_agent.parallel_num") + save_dir_comp = webui_manager.get_component_by_id( + "deep_research_agent.max_query" + ) # Note: component ID seems misnamed in original code + start_button_comp = webui_manager.get_component_by_id("deep_research_agent.start_button") + stop_button_comp = webui_manager.get_component_by_id("deep_research_agent.stop_button") + markdown_display_comp = webui_manager.get_component_by_id( + "deep_research_agent.markdown_display" + ) + markdown_download_comp = webui_manager.get_component_by_id( + "deep_research_agent.markdown_download" + ) + mcp_server_config_comp = webui_manager.get_component_by_id( + "deep_research_agent.mcp_server_config" + ) + + # --- 1. Get Task and Settings --- + task_topic = components.get(research_task_comp, "").strip() + task_id_to_resume = components.get(resume_task_id_comp, "").strip() or None + max_parallel_agents = int(components.get(parallel_num_comp, 1)) + base_save_dir = components.get(save_dir_comp, "./tmp/deep_research").strip() + safe_root_dir = "./tmp/deep_research" + normalized_base_save_dir = os.path.abspath(os.path.normpath(base_save_dir)) + if os.path.commonpath( + [normalized_base_save_dir, os.path.abspath(safe_root_dir)] + ) != os.path.abspath(safe_root_dir): + logger.warning(f"Unsafe base_save_dir detected: {base_save_dir}. Using default directory.") + normalized_base_save_dir = os.path.abspath(safe_root_dir) + base_save_dir = normalized_base_save_dir + mcp_server_config_str = components.get(mcp_server_config_comp) + mcp_config = json.loads(mcp_server_config_str) if mcp_server_config_str else None + + if not task_topic: + gr.Warning("Please enter a research task.") + yield {start_button_comp: gr.update(interactive=True)} # Re-enable start button + return + + # Store base save dir for stop handler + webui_manager.dr_save_dir = base_save_dir + os.makedirs(base_save_dir, exist_ok=True) + + # --- 2. Initial UI Update --- + yield { + start_button_comp: gr.update(value="⏳ Running...", interactive=False), + stop_button_comp: gr.update(interactive=True), + research_task_comp: gr.update(interactive=False), + resume_task_id_comp: gr.update(interactive=False), + parallel_num_comp: gr.update(interactive=False), + save_dir_comp: gr.update(interactive=False), + markdown_display_comp: gr.update(value="Starting research..."), + markdown_download_comp: gr.update(value=None, interactive=False), + } + + agent_task = None + running_task_id = None + plan_file_path = None + report_file_path = None + last_plan_content = None + last_plan_mtime = 0 + + try: + # --- 3. Get LLM and Browser Config from other tabs --- + # Access settings values via components dict, getting IDs from webui_manager + def get_setting(tab: str, key: str, default: Any = None): + comp = webui_manager.id_to_component.get(f"{tab}.{key}") + return components.get(comp, default) if comp else default + + # LLM Config (from agent_settings tab) + llm_provider_name = get_setting("agent_settings", "llm_provider") + llm_model_name = get_setting("agent_settings", "llm_model_name") + llm_temperature = max(get_setting("agent_settings", "llm_temperature", 0.5), 0.5) + llm_base_url = get_setting("agent_settings", "llm_base_url") + llm_api_key = get_setting("agent_settings", "llm_api_key") + ollama_num_ctx = get_setting("agent_settings", "ollama_num_ctx") + + llm = await _initialize_llm( + llm_provider_name, + llm_model_name, + llm_temperature, + llm_base_url, + llm_api_key, + ollama_num_ctx if llm_provider_name == "ollama" else None, + ) + if not llm: + raise ValueError("LLM Initialization failed. Please check Agent Settings.") + + # Browser Config (from browser_settings tab) + # Note: DeepResearchAgent constructor takes a dict, not full Browser/Context objects + browser_config_dict = { + "headless": get_setting("browser_settings", "headless", False), + "disable_security": get_setting("browser_settings", "disable_security", False), + "browser_binary_path": get_setting("browser_settings", "browser_binary_path"), + "user_data_dir": get_setting("browser_settings", "browser_user_data_dir"), + "window_width": int(get_setting("browser_settings", "window_w", 1280)), + "window_height": int(get_setting("browser_settings", "window_h", 1100)), + # Add other relevant fields if DeepResearchAgent accepts them + } + + # --- 4. Initialize or Get Agent --- + if not webui_manager.dr_agent: + webui_manager.dr_agent = DeepResearchAgent( + llm=llm, browser_config=browser_config_dict, mcp_server_config=mcp_config + ) + logger.info("DeepResearchAgent initialized.") + + # --- 5. Start Agent Run --- + agent_run_coro = webui_manager.dr_agent.run( + topic=task_topic, + task_id=task_id_to_resume, + save_dir=base_save_dir, + max_parallel_browsers=max_parallel_agents, + ) + agent_task = asyncio.create_task(agent_run_coro) + webui_manager.dr_current_task = agent_task + + # Wait briefly for the agent to start and potentially create the task ID/folder + await asyncio.sleep(1.0) + + # Determine the actual task ID being used (agent sets this) + running_task_id = webui_manager.dr_agent.current_task_id + if not running_task_id: + # Agent might not have set it yet, try to get from result later? Risky. + # Or derive from resume_task_id if provided? + running_task_id = task_id_to_resume + if not running_task_id: + logger.warning("Could not determine running task ID immediately.") + # We can still monitor, but might miss initial plan if ID needed for path + else: + logger.info(f"Assuming task ID based on resume ID: {running_task_id}") + else: + logger.info(f"Agent started with Task ID: {running_task_id}") + + webui_manager.dr_task_id = running_task_id # Store for stop handler + + # --- 6. Monitor Progress via research_plan.md --- + if running_task_id: + task_specific_dir = os.path.join(base_save_dir, str(running_task_id)) + plan_file_path = os.path.join(task_specific_dir, "research_plan.md") + report_file_path = os.path.join(task_specific_dir, "report.md") + logger.info(f"Monitoring plan file: {plan_file_path}") + else: + logger.warning("Cannot monitor plan file: Task ID unknown.") + plan_file_path = None + last_plan_content = None + while not agent_task.done(): + update_dict = {} + update_dict[resume_task_id_comp] = gr.update(value=running_task_id) + agent_stopped = getattr(webui_manager.dr_agent, "stopped", False) + if agent_stopped: + logger.info("Stop signal detected from agent state.") + break # Exit monitoring loop + + # Check and update research plan display + if plan_file_path: + try: + current_mtime = ( + os.path.getmtime(plan_file_path) if os.path.exists(plan_file_path) else 0 + ) + if current_mtime > last_plan_mtime: + logger.info(f"Detected change in {plan_file_path}") + plan_content = _read_file_safe(plan_file_path) + if last_plan_content is None or ( + plan_content is not None and plan_content != last_plan_content + ): + update_dict[markdown_display_comp] = gr.update(value=plan_content) + last_plan_content = plan_content + last_plan_mtime = current_mtime + elif plan_content is None: + # File might have been deleted or became unreadable + last_plan_mtime = 0 # Reset to force re-read attempt later + except Exception as e: + logger.warning(f"Error checking/reading plan file {plan_file_path}: {e}") + # Avoid continuous logging for the same error + await asyncio.sleep(2.0) + + # Yield updates if any + if update_dict: + yield update_dict + + await asyncio.sleep(1.0) # Check file changes every second + + # --- 7. Task Finalization --- + logger.info("Agent task processing finished. Awaiting final result...") + final_result_dict = await agent_task # Get result or raise exception + logger.info( + f"Agent run completed. Result keys: {final_result_dict.keys() if final_result_dict else 'None'}" + ) + + # Try to get task ID from result if not known before + if not running_task_id and final_result_dict and "task_id" in final_result_dict: + running_task_id = final_result_dict["task_id"] + webui_manager.dr_task_id = running_task_id + task_specific_dir = os.path.join(base_save_dir, str(running_task_id)) + report_file_path = os.path.join(task_specific_dir, "report.md") + logger.info(f"Task ID confirmed from result: {running_task_id}") + + final_ui_update = {} + if report_file_path and os.path.exists(report_file_path): + logger.info(f"Loading final report from: {report_file_path}") + report_content = _read_file_safe(report_file_path) + if report_content: + final_ui_update[markdown_display_comp] = gr.update(value=report_content) + final_ui_update[markdown_download_comp] = gr.File( + value=report_file_path, label=f"Report ({running_task_id}.md)", interactive=True + ) + else: + final_ui_update[markdown_display_comp] = gr.update( + value="# Research Complete\n\n*Error reading final report file.*" + ) + elif final_result_dict and "report" in final_result_dict: + logger.info("Using report content directly from agent result.") + # If agent directly returns report content + final_ui_update[markdown_display_comp] = gr.update(value=final_result_dict["report"]) + # Cannot offer download if only content is available + final_ui_update[markdown_download_comp] = gr.update( + value=None, label="Download Research Report", interactive=False + ) + else: + logger.warning("Final report file not found and not in result dict.") + final_ui_update[markdown_display_comp] = gr.update( + value="# Research Complete\n\n*Final report not found.*" + ) + + yield final_ui_update + + except Exception as e: + logger.error(f"Error during Deep Research Agent execution: {e}", exc_info=True) + gr.Error(f"Research failed: {e}") + yield { + markdown_display_comp: gr.update( + value=f"# Research Failed\n\n**Error:**\n```\n{e}\n```" + ) + } + + finally: + # --- 8. Final UI Reset --- + webui_manager.dr_current_task = None # Clear task reference + webui_manager.dr_task_id = None # Clear running task ID + + yield { + start_button_comp: gr.update(value="▶️ Run", interactive=True), + stop_button_comp: gr.update(interactive=False), + research_task_comp: gr.update(interactive=True), + resume_task_id_comp: gr.update(value="", interactive=True), + parallel_num_comp: gr.update(interactive=True), + save_dir_comp: gr.update(interactive=True), + # Keep download button enabled if file exists + markdown_download_comp: gr.update() + if report_file_path and os.path.exists(report_file_path) + else gr.update(interactive=False), + } + + +async def stop_deep_research(webui_manager: WebuiManager) -> dict[Component, Any]: + """Handles the Stop button click.""" + logger.info("Stop button clicked for Deep Research.") + agent = webui_manager.dr_agent + task = webui_manager.dr_current_task + task_id = webui_manager.dr_task_id + base_save_dir = webui_manager.dr_save_dir + + stop_button_comp = webui_manager.get_component_by_id("deep_research_agent.stop_button") + start_button_comp = webui_manager.get_component_by_id("deep_research_agent.start_button") + markdown_display_comp = webui_manager.get_component_by_id( + "deep_research_agent.markdown_display" + ) + markdown_download_comp = webui_manager.get_component_by_id( + "deep_research_agent.markdown_download" + ) + + final_update = {stop_button_comp: gr.update(interactive=False, value="⏹️ Stopping...")} + + if agent and task and not task.done(): + logger.info("Signalling DeepResearchAgent to stop.") + try: + # Assuming stop is synchronous or sets a flag quickly + await agent.stop() + except Exception as e: + logger.error(f"Error calling agent.stop(): {e}") + + # The run_deep_research loop should detect the stop and exit. + # We yield an intermediate "Stopping..." state. The final reset is done by run_deep_research. + + # Try to show the final report if available after stopping + await asyncio.sleep(1.5) # Give agent a moment to write final files potentially + report_file_path = None + if task_id and base_save_dir: + report_file_path = os.path.join(base_save_dir, str(task_id), "report.md") + + if report_file_path and os.path.exists(report_file_path): + report_content = _read_file_safe(report_file_path) + if report_content: + final_update[markdown_display_comp] = gr.update( + value=report_content + "\n\n---\n*Research stopped by user.*" + ) + final_update[markdown_download_comp] = gr.File( + value=report_file_path, label=f"Report ({task_id}.md)", interactive=True + ) + else: + final_update[markdown_display_comp] = gr.update( + value="# Research Stopped\n\n*Error reading final report file after stop.*" + ) + else: + final_update[markdown_display_comp] = gr.update(value="# Research Stopped by User") + + # Keep start button disabled, run_deep_research finally block will re-enable it. + final_update[start_button_comp] = gr.update(interactive=False) + + else: + logger.warning("Stop clicked but no active research task found.") + # Reset UI state just in case + final_update = { + start_button_comp: gr.update(interactive=True), + stop_button_comp: gr.update(interactive=False), + webui_manager.get_component_by_id("deep_research_agent.research_task"): gr.update( + interactive=True + ), + webui_manager.get_component_by_id("deep_research_agent.resume_task_id"): gr.update( + interactive=True + ), + webui_manager.get_component_by_id("deep_research_agent.max_iteration"): gr.update( + interactive=True + ), + webui_manager.get_component_by_id("deep_research_agent.max_query"): gr.update( + interactive=True + ), + } + + return final_update + + +async def update_mcp_server(mcp_file: str, webui_manager: WebuiManager): + """ + Update the MCP server. + """ + if hasattr(webui_manager, "dr_agent") and webui_manager.dr_agent: + logger.warning("⚠️ Close controller because mcp file has changed!") + await webui_manager.dr_agent.close_mcp_client() + + if not mcp_file or not os.path.exists(mcp_file) or not mcp_file.endswith(".json"): + logger.warning(f"{mcp_file} is not a valid MCP file.") + return None, gr.update(visible=False) + + with open(mcp_file) as f: + mcp_server = json.load(f) + + return json.dumps(mcp_server, indent=2), gr.update(visible=True) + + +def create_deep_research_agent_tab(webui_manager: WebuiManager): + """ + Creates a deep research agent tab + """ + tab_components = {} + + with gr.Group(): + with gr.Row(): + mcp_json_file = gr.File(label="MCP server json", interactive=True, file_types=[".json"]) + mcp_server_config = gr.Textbox( + label="MCP server", lines=6, interactive=True, visible=False + ) + + with gr.Group(): + research_task = gr.Textbox( + label="Research Task", + lines=5, + value="Give me a detailed travel plan to Switzerland from June 1st to 10th.", + interactive=True, + ) + with gr.Row(): + resume_task_id = gr.Textbox(label="Resume Task ID", value="", interactive=True) + parallel_num = gr.Number( + label="Parallel Agent Num", value=1, precision=0, interactive=True + ) + max_query = gr.Textbox( + label="Research Save Dir", value="./tmp/deep_research", interactive=True + ) + with gr.Row(): + stop_button = gr.Button("⏹️ Stop", variant="stop", scale=2) + start_button = gr.Button("▶️ Run", variant="primary", scale=3) + with gr.Group(): + markdown_display = gr.Markdown(label="Research Report") + markdown_download = gr.File(label="Download Research Report", interactive=False) + tab_components.update( + { + "research_task": research_task, + "parallel_num": parallel_num, + "max_query": max_query, + "start_button": start_button, + "stop_button": stop_button, + "markdown_display": markdown_display, + "markdown_download": markdown_download, + "resume_task_id": resume_task_id, + "mcp_json_file": mcp_json_file, + "mcp_server_config": mcp_server_config, + } + ) + webui_manager.add_components("deep_research_agent", tab_components) + webui_manager.init_deep_research_agent() + + async def update_wrapper(mcp_file): + """Wrapper for handle_pause_resume.""" + update_dict = await update_mcp_server(mcp_file, webui_manager) + yield update_dict + + mcp_json_file.change( + update_wrapper, inputs=[mcp_json_file], outputs=[mcp_server_config, mcp_server_config] + ) + + dr_tab_outputs = list(tab_components.values()) + all_managed_inputs = set(webui_manager.get_components()) + + # --- Define Event Handler Wrappers --- + def start_wrapper(*args) -> AsyncGenerator[dict[Component, Any]]: + # Convert individual component values to components dict + comps = {} + all_components = list(all_managed_inputs) + for i, comp in enumerate(all_components): + if i < len(args): + comps[comp] = args[i] + + async def _async_wrapper(): + async for update in run_deep_research(webui_manager, comps): + yield update + + return _async_wrapper() + + async def stop_wrapper() -> AsyncGenerator[dict[Component, Any]]: + update_dict = await stop_deep_research(webui_manager) + yield update_dict + + # --- Connect Handlers --- + start_button.click(fn=start_wrapper, inputs=list(all_managed_inputs), outputs=dr_tab_outputs) + + stop_button.click(fn=stop_wrapper, inputs=None, outputs=dr_tab_outputs) diff --git a/src/web_ui/webui/components/help_modal.py b/src/web_ui/webui/components/help_modal.py new file mode 100644 index 00000000..7f4e43b4 --- /dev/null +++ b/src/web_ui/webui/components/help_modal.py @@ -0,0 +1,234 @@ +""" +Help Modal Component + +Provides Getting Started guide, keyboard shortcuts, and troubleshooting tips. +""" + +import gradio as gr + +from src.web_ui.webui.webui_manager import WebuiManager + + +def create_help_modal(webui_manager: WebuiManager): + """ + Create a help modal with Getting Started guide and keyboard shortcuts. + + Args: + webui_manager: WebUI manager instance + + Returns: + dict: Modal components + """ + modal_components = {} + + # Modal dialog (initially hidden) + with gr.Group(visible=False, elem_classes=["help-modal-overlay"]) as help_modal: + with gr.Column(elem_classes=["help-modal-content"]): + gr.Markdown("# 🎓 Getting Started with Browser Use WebUI") + + with gr.Tabs(): + with gr.TabItem("📖 Quick Start"): + gr.Markdown( + """ + ## Welcome! + + Browser Use WebUI allows AI agents to control web browsers and perform tasks automatically. + + ### First Time Setup: + + 1. **Configure Your LLM** (if not in .env) + - Click the ⚙️ Settings toggle on the right + - Expand "🤖 LLM Configuration" + - Select your provider (OpenAI, Anthropic, Google, etc.) + - Add your API key if needed + + 2. **Choose Your Mode** + - Use **Quick Presets** in the left sidebar for common scenarios + - OR manually configure in Settings panel + + 3. **Run Your First Task** + - Enter a task description in the main area + - Click "▶️ Run Agent" + - Watch the agent work! + + ### Quick Presets: + + - **🔬 Research Mode**: Claude Sonnet, high creativity, 150 max steps + - **🤖 Automation Mode**: GPT-4o, balanced, 100 max steps + - **🌐 Custom Browser**: Use your own Chrome profile for authenticated sites + + ### Tips: + + - **Vision Mode**: Enable to let the LLM see screenshots (better accuracy) + - **Custom Browser**: Access logged-in websites using your browser profile + - **MCP Servers**: Add filesystem, fetch, or brave-search for extended capabilities + - **Max Steps**: Increase for complex multi-step tasks + - **Save Configs**: Save your favorite setups via Settings panel + """ + ) + + with gr.TabItem("⌨️ Keyboard Shortcuts"): + gr.Markdown( + """ + ## Keyboard Shortcuts + + Speed up your workflow with these keyboard shortcuts: + + | Shortcut | Action | + |----------|--------| + | `Ctrl + Enter` | Submit task (when in textarea) | + | `Esc` | Stop agent execution | + | `?` | Show/hide this help modal | + + ### Agent Control: + + During agent execution, you can: + - Press `Ctrl+C` in the terminal to pause the agent + - Type 'r' to resume + - Type 'q' to quit + + *(Note: Terminal shortcuts only work when running via command line)* + """ + ) + + with gr.TabItem("🎯 Use Cases"): + gr.Markdown( + """ + ## Common Use Cases + + ### 🔍 Web Research + - **Agent**: Deep Research Agent (Agent Marketplace) + - **Settings**: Claude Sonnet or GPT-4, temperature 0.7-0.8 + - **MCP**: Enable brave-search or fetch for web access + - **Example Task**: "Research the latest trends in AI safety" + + ### 🤖 Browser Automation + - **Agent**: Browser Use Agent + - **Settings**: GPT-4o, temperature 0.5-0.6, vision enabled + - **Custom Browser**: Enable if accessing authenticated sites + - **Example Task**: "Fill out this form with my information" + + ### 📊 Data Extraction + - **Agent**: Browser Use Agent + - **Settings**: Any vision-capable model, temperature 0.5 + - **MCP**: Enable filesystem to save extracted data + - **Example Task**: "Extract all product prices from this website" + + ### 🧪 Testing & QA + - **Agent**: Browser Use Agent + - **Settings**: GPT-4o-mini (cost-effective), vision enabled + - **Custom Browser**: Use if testing authenticated flows + - **Example Task**: "Test the login flow and report any issues" + """ + ) + + with gr.TabItem("🔧 Troubleshooting"): + gr.Markdown( + """ + ## Common Issues & Solutions + + ### "No API key configured" + **Solution**: Add your API key in Settings > LLM Configuration > API Credentials, or set environment variables in `.env` file. + + ### "Browser failed to start" + **Solutions**: + - Ensure Playwright is installed: `playwright install chromium --with-deps` + - If using Custom Browser mode, close all Chrome windows first + - Check browser binary path is correct + + ### "Agent gets stuck in a loop" + **Solutions**: + - Reduce `Max Steps` to limit iterations + - Lower `Temperature` for more deterministic behavior + - Try a different LLM model + - Click "Stop" and rephrase your task more specifically + + ### "Vision/screenshots not working" + **Solutions**: + - Ensure "Enable Vision" is checked in Settings + - Verify your LLM model supports vision (e.g., GPT-4o, Claude Sonnet) + - Check that browser is not in headless mode if you need to see what's happening + + ### "MCP tools not appearing" + **Solutions**: + - Verify `data/mcp.json` exists and is valid (use MCP Settings tab) + - Check that required environment variables (API keys) are set + - Use "Clear" button to restart the agent with new MCP configuration + + ### "Custom browser mode not working" + **Solutions**: + - Close ALL Chrome/browser windows before starting + - Verify browser binary path is correct + - Ensure user data directory exists + - Open the WebUI in a different browser (Firefox/Edge) + + ### Still having issues? + - Check the browser console (F12) for errors + - Review terminal logs for detailed error messages + - Consult CLAUDE.md in the project root for architecture details + """ + ) + + with gr.TabItem("📚 Resources"): + gr.Markdown( + """ + ## Additional Resources + + ### Documentation + - **Project README**: See `README.md` in project root + - **Claude Code Guide**: See `CLAUDE.md` for architecture details + - **MCP Documentation**: See `mcp.example.json` for server examples + + ### Links + - **Browser Use Library**: [github.com/browser-use/browser-use](https://github.com/browser-use/browser-use) + - **Model Context Protocol**: [modelcontextprotocol.io](https://modelcontextprotocol.io) + - **LangChain Docs**: [python.langchain.com](https://python.langchain.com) + + ### Community + - Report issues on GitHub + - Contribute improvements via Pull Requests + - Share your custom agents and MCP server configs + + ### Environment Variables + + Key environment variables in `.env`: + ``` + DEFAULT_LLM=openai + OPENAI_API_KEY=sk-... + ANTHROPIC_API_KEY=sk-ant-... + GOOGLE_API_KEY=... + + USE_OWN_BROWSER=false + KEEP_BROWSER_OPEN=true + BROWSER_USE_LOGGING_LEVEL=info + ``` + + See `.env.example` for full list. + """ + ) + + # Close button + with gr.Row(): + close_help_button = gr.Button("Close", variant="primary", size="lg") + + modal_components.update( + { + "help_modal": help_modal, + "close_help_button": close_help_button, + } + ) + + webui_manager.add_components("help_modal", modal_components) + + # Close button handler + def close_modal(): + """Hide the help modal.""" + return gr.update(visible=False) + + close_help_button.click( + fn=close_modal, + inputs=[], + outputs=[help_modal], + ) + + return modal_components diff --git a/src/web_ui/webui/components/load_save_config_tab.py b/src/web_ui/webui/components/load_save_config_tab.py new file mode 100644 index 00000000..3d967935 --- /dev/null +++ b/src/web_ui/webui/components/load_save_config_tab.py @@ -0,0 +1,52 @@ +import gradio as gr + +from src.web_ui.webui.webui_manager import WebuiManager + + +def create_load_save_config_tab(webui_manager: WebuiManager): + """ + Creates a load and save config tab. + """ + tab_components = {} + + config_file = gr.File( + label="Load UI Settings from json", file_types=[".json"], interactive=True + ) + with gr.Row(): + load_config_button = gr.Button("Load Config", variant="primary") + save_config_button = gr.Button("Save UI Settings", variant="primary") + + config_status = gr.Textbox(label="Status", lines=2, interactive=False) + + tab_components.update( + { + "load_config_button": load_config_button, + "save_config_button": save_config_button, + "config_status": config_status, + "config_file": config_file, + } + ) + + webui_manager.add_components("load_save_config", tab_components) + + def save_config_wrapper(*args): + """Wrapper for save_config that accepts individual component values.""" + # Convert individual component values to a components dict + components_dict = {} + all_components = webui_manager.get_components() + for i, comp in enumerate(all_components): + if i < len(args): + components_dict[comp] = args[i] + return webui_manager.save_config(components_dict) + + save_config_button.click( + fn=save_config_wrapper, + inputs=list(webui_manager.get_components()), + outputs=[config_status], + ) + + load_config_button.click( + fn=webui_manager.load_config, + inputs=[config_file], + outputs=webui_manager.get_components(), + ) diff --git a/src/web_ui/webui/components/mcp_settings_tab.py b/src/web_ui/webui/components/mcp_settings_tab.py new file mode 100644 index 00000000..8d0729ee --- /dev/null +++ b/src/web_ui/webui/components/mcp_settings_tab.py @@ -0,0 +1,407 @@ +""" +MCP Settings Tab Component + +Provides UI for editing MCP (Model Context Protocol) server configuration. +""" + +import json +import logging +from pathlib import Path + +import gradio as gr + +from src.web_ui.utils.mcp_config import ( + get_default_mcp_config, + get_mcp_config_path, + get_mcp_config_summary, + load_mcp_config, + save_mcp_config, + validate_mcp_config, +) +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + + +def load_mcp_config_ui(custom_path: str | None = None): + """ + Load MCP configuration for UI display. + + Args: + custom_path: Optional custom path to load from + + Returns: + Tuple of (config_json_str, status_message, validation_message) + """ + try: + # Determine which path to use + if custom_path and custom_path.strip(): + config_path = Path(custom_path.strip()) + else: + config_path = get_mcp_config_path() + + # Load configuration + config = load_mcp_config(config_path) + + if config is None: + # File doesn't exist or is invalid, use default + config = get_default_mcp_config() + status = ( + f"⚠️ No configuration found at {config_path}. Using default empty configuration." + ) + validation = "✅ Valid (default configuration)" + else: + status = f"✅ Loaded configuration from {config_path}" + validation = "✅ Valid configuration" + + # Convert to pretty JSON string + config_json = json.dumps(config, indent=2, ensure_ascii=False) + + return ( + config_json, + status, + validation, + get_mcp_config_summary(config), + ) + + except Exception as e: + logger.error(f"Error loading MCP configuration: {e}", exc_info=True) + default_config = get_default_mcp_config() + return ( + json.dumps(default_config, indent=2), + f"❌ Error loading configuration: {e}", + "⚠️ Using default configuration", + "", + ) + + +def save_mcp_config_ui(config_text: str, custom_path: str | None = None): + """ + Save MCP configuration from UI. + + Args: + config_text: JSON configuration text + custom_path: Optional custom path to save to + + Returns: + Tuple of (status_message, validation_message) + """ + try: + # Parse JSON + try: + config = json.loads(config_text) + except json.JSONDecodeError as e: + return ( + f"❌ Invalid JSON: {e}", + "❌ Cannot save invalid JSON", + "", + ) + + # Validate configuration + is_valid, error_msg = validate_mcp_config(config) + if not is_valid: + return ( + f"❌ Invalid configuration: {error_msg}", + "❌ Cannot save invalid configuration", + "", + ) + + # Determine save path + if custom_path and custom_path.strip(): + config_path = Path(custom_path.strip()) + else: + config_path = get_mcp_config_path() + + # Save configuration + success = save_mcp_config(config, config_path) + + if success: + return ( + f"✅ Configuration saved to {config_path}", + "✅ Valid configuration", + get_mcp_config_summary(config), + ) + else: + return ( + f"❌ Failed to save configuration to {config_path}", + "⚠️ Configuration is valid but save failed", + "", + ) + + except Exception as e: + logger.error(f"Error saving MCP configuration: {e}", exc_info=True) + return ( + f"❌ Error: {e}", + "❌ Save failed", + "", + ) + + +def validate_mcp_config_ui(config_text: str): + """ + Validate MCP configuration from UI. + + Args: + config_text: JSON configuration text + + Returns: + Validation message + """ + try: + # Parse JSON + try: + config = json.loads(config_text) + except json.JSONDecodeError as e: + return ( + f"❌ Invalid JSON: {e}", + "", + ) + + # Validate configuration + is_valid, error_msg = validate_mcp_config(config) + + if is_valid: + return ( + "✅ Valid configuration", + get_mcp_config_summary(config), + ) + else: + return ( + f"❌ Invalid configuration: {error_msg}", + "", + ) + + except Exception as e: + logger.error(f"Error validating MCP configuration: {e}", exc_info=True) + return ( + f"❌ Validation error: {e}", + "", + ) + + +def reset_mcp_config_ui(): + """ + Reset MCP configuration to default. + + Returns: + Tuple of (config_json_str, status_message, validation_message, summary) + """ + default_config = get_default_mcp_config() + config_json = json.dumps(default_config, indent=2, ensure_ascii=False) + + return ( + config_json, + "⚠️ Reset to default configuration (not saved)", + "✅ Valid (default configuration)", + get_mcp_config_summary(default_config), + ) + + +def load_example_config_ui(): + """ + Load example MCP configuration. + + Returns: + Tuple of (config_json_str, status_message, validation_message, summary) + """ + try: + example_path = Path("mcp.example.json") + + if not example_path.exists(): + return ( + gr.update(), # Don't change editor content + "❌ mcp.example.json not found", + "⚠️ Example file not available", + "", + ) + + with open(example_path, encoding="utf-8") as f: + config = json.load(f) + + config_json = json.dumps(config, indent=2, ensure_ascii=False) + + return ( + config_json, + "ℹ️ Loaded example configuration (not saved). Edit and save as needed.", + "✅ Valid configuration", + get_mcp_config_summary(config), + ) + + except Exception as e: + logger.error(f"Error loading example configuration: {e}", exc_info=True) + return ( + gr.update(), + f"❌ Error loading example: {e}", + "", + "", + ) + + +def create_mcp_settings_tab(webui_manager: WebuiManager): + """ + Create the MCP Settings tab for editing MCP server configuration. + + Args: + webui_manager: WebUI manager instance + """ + tab_components = {} + + with gr.Column(): + gr.Markdown( + """ + # MCP Settings + + Configure Model Context Protocol (MCP) servers that provide additional tools and capabilities to agents. + + **Quick Start:** + 1. Click "Load Example Config" to see available MCP servers + 2. Edit the configuration to enable/disable servers + 3. Add API keys where needed (in `env` fields) + 4. Click "Save Configuration" + 5. Restart agents to use new MCP tools + """ + ) + + with gr.Row(): + config_path_input = gr.Textbox( + label="Configuration File Path", + value=str(get_mcp_config_path()), + placeholder="Leave empty for default (./data/mcp.json)", + scale=3, + ) + load_button = gr.Button("🔄 Load", scale=1, variant="secondary") + + status_message = gr.Markdown("ℹ️ Ready to load or create configuration") + + mcp_config_editor = gr.Code( + label="MCP Configuration (JSON)", + language="json", + lines=20, + value="{}", + ) + + validation_message = gr.Markdown("ℹ️ Edit configuration above") + + with gr.Row(): + save_button = gr.Button("💾 Save Configuration", variant="primary", scale=2) + validate_button = gr.Button("✓ Validate", variant="secondary", scale=1) + reset_button = gr.Button("↺ Reset to Default", variant="secondary", scale=1) + example_button = gr.Button("📖 Load Example Config", variant="secondary", scale=2) + + with gr.Accordion("Server Summary", open=False): + server_summary = gr.Markdown("No servers configured") + + gr.Markdown( + """ + --- + + ### Common MCP Servers + + - **filesystem**: Access local files and directories + - **fetch**: Make HTTP requests to external APIs + - **puppeteer**: Browser automation capabilities + - **brave-search**: Web search via Brave Search API + - **github**: GitHub repository operations + - **postgres/sqlite**: Database operations + - **memory**: Persistent memory for agents + - **sequential-thinking**: Enhanced reasoning capabilities + + See `mcp.example.json` for full configuration examples. + + ### Configuration Format + + ```json + { + "mcpServers": { + "server-name": { + "command": "npx", + "args": ["-y", "@modelcontextprotocol/server-name"], + "env": { + "API_KEY": "your_key_here" + } + } + } + } + ``` + + ⚠️ **Important**: After changing MCP configuration, you must restart agents for changes to take effect. + Use the "Clear" button in the Browser Use Agent tab to reset the agent. + """ + ) + + # Store components + tab_components.update( + { + "config_path_input": config_path_input, + "load_button": load_button, + "save_button": save_button, + "validate_button": validate_button, + "reset_button": reset_button, + "example_button": example_button, + "mcp_config_editor": mcp_config_editor, + "status_message": status_message, + "validation_message": validation_message, + "server_summary": server_summary, + } + ) + webui_manager.add_components("mcp_settings", tab_components) + + # Connect event handlers + load_button.click( + fn=load_mcp_config_ui, + inputs=[config_path_input], + outputs=[ + mcp_config_editor, + status_message, + validation_message, + server_summary, + ], + ) + + save_button.click( + fn=save_mcp_config_ui, + inputs=[mcp_config_editor, config_path_input], + outputs=[ + status_message, + validation_message, + server_summary, + ], + ) + + validate_button.click( + fn=validate_mcp_config_ui, + inputs=[mcp_config_editor], + outputs=[ + validation_message, + server_summary, + ], + ) + + reset_button.click( + fn=reset_mcp_config_ui, + inputs=[], + outputs=[ + mcp_config_editor, + status_message, + validation_message, + server_summary, + ], + ) + + example_button.click( + fn=load_example_config_ui, + inputs=[], + outputs=[ + mcp_config_editor, + status_message, + validation_message, + server_summary, + ], + ) + + # Load configuration on tab creation + initial_config_json, initial_status, initial_validation, initial_summary = load_mcp_config_ui() + mcp_config_editor.value = initial_config_json + status_message.value = initial_status + validation_message.value = initial_validation + server_summary.value = initial_summary diff --git a/src/web_ui/webui/components/quick_start_tab.py b/src/web_ui/webui/components/quick_start_tab.py new file mode 100644 index 00000000..b70aefb5 --- /dev/null +++ b/src/web_ui/webui/components/quick_start_tab.py @@ -0,0 +1,423 @@ +""" +Quick Start Tab Component + +Provides a landing page with preset configurations, status display, and quick actions. +""" + +import logging +import os + +import gradio as gr + +from src.web_ui.utils.mcp_config import get_mcp_config_path, load_mcp_config +from src.web_ui.webui.webui_manager import WebuiManager + +logger = logging.getLogger(__name__) + +# Preset configurations +PRESETS = { + "research": { + "name": "🔬 Research Mode", + "description": "Optimized for deep research tasks with comprehensive analysis", + "config": { + "llm_provider": "anthropic", + "llm_model_name": "claude-3-5-sonnet-20241022", + "llm_temperature": 0.7, + "use_vision": True, + "max_steps": 150, + "max_actions": 10, + "headless": False, + "keep_browser_open": True, + }, + }, + "automation": { + "name": "🤖 Automation Mode", + "description": "Fast and efficient for browser automation tasks", + "config": { + "llm_provider": "openai", + "llm_model_name": "gpt-4o", + "llm_temperature": 0.6, + "use_vision": True, + "max_steps": 100, + "max_actions": 10, + "headless": False, + "keep_browser_open": True, + }, + }, + "custom_browser": { + "name": "🌐 Custom Browser Mode", + "description": "Use your own Chrome profile for authenticated sessions", + "config": { + "llm_provider": "openai", + "llm_model_name": "gpt-4o-mini", + "llm_temperature": 0.6, + "use_vision": True, + "max_steps": 100, + "max_actions": 10, + "use_own_browser": True, + "keep_browser_open": True, + "headless": False, + }, + }, +} + + +def get_current_config_status() -> str: + """ + Get current configuration status from environment. + + Returns: + Markdown string with configuration status + """ + try: + # Check LLM configuration + default_llm = os.getenv("DEFAULT_LLM", "openai") + api_key_var = f"{default_llm.upper()}_API_KEY" + api_key_set = bool(os.getenv(api_key_var)) + + llm_status = "✅ Configured" if api_key_set else "⚠️ No API key" + llm_display = default_llm.title() + + # Check MCP configuration + get_mcp_config_path() # Check if config exists + mcp_config = load_mcp_config() + if mcp_config and "mcpServers" in mcp_config: + mcp_count = len(mcp_config["mcpServers"]) + mcp_status = f"✅ {mcp_count} server(s) configured" + else: + mcp_status = "ℹ️ Not configured (optional)" + + # Check browser configuration + use_own_browser = os.getenv("USE_OWN_BROWSER", "false").lower() == "true" + browser_status = "Custom Chrome" if use_own_browser else "Default Playwright" + + status_md = f""" +**Current Configuration:** + +- **LLM Provider:** {llm_display} {llm_status} +- **Browser:** {browser_status} +- **MCP Servers:** {mcp_status} + +💡 **Tip:** Use preset configurations below to quickly set up common scenarios, or configure settings manually in the Settings tab. +""" + return status_md + + except Exception as e: + logger.error(f"Error getting config status: {e}", exc_info=True) + return """ +**Current Configuration:** + +⚠️ Error reading configuration. Please check your .env file. +""" + + +def load_preset_config(preset_name: str, webui_manager: WebuiManager): + """ + Load a preset configuration and return component updates. + + Args: + preset_name: Name of the preset to load + webui_manager: WebUI manager instance + + Returns: + List of gr.update() objects for each component + """ + if preset_name not in PRESETS: + logger.warning(f"Unknown preset: {preset_name}") + return [] + + preset = PRESETS[preset_name] + preset_config = preset["config"] + + # Map preset values to component IDs and create updates + updates = [] + + # Get all components that need updating + component_mapping = { + "llm_provider": "agent_settings.llm_provider", + "llm_model_name": "agent_settings.llm_model_name", + "llm_temperature": "agent_settings.llm_temperature", + "use_vision": "agent_settings.use_vision", + "max_steps": "agent_settings.max_steps", + "max_actions": "agent_settings.max_actions", + "headless": "browser_settings.headless", + "keep_browser_open": "browser_settings.keep_browser_open", + "use_own_browser": "browser_settings.use_own_browser", + } + + for config_key, component_id in component_mapping.items(): + if config_key in preset_config: + try: + component = webui_manager.get_component_by_id(component_id) + if isinstance(preset_config, dict): + updates.append((component, preset_config[config_key])) + except KeyError: + logger.debug(f"Component not found: {component_id}") + continue + + return updates + + +def create_quick_start_tab(webui_manager: WebuiManager): + """ + Creates a Quick Start tab with status display and preset configurations. + + Args: + webui_manager: WebUI manager instance + """ + tab_components = {} + + # Header + gr.Markdown( + """ + ## 🚀 Welcome to Browser Use WebUI + Get started quickly with preset configurations or jump directly to your desired section. + """, + elem_classes=["tab-header-text"], + ) + + with gr.Row(): + # Left column: Quick Actions + with gr.Column(scale=1): + gr.Markdown("### 📋 Quick Actions") + + with gr.Group(): + gr.Markdown("**Preset Configurations**") + gr.Markdown( + "Load optimized settings for common use cases. These will populate the Settings tab." + ) + + research_btn = gr.Button( + "🔬 Load Research Mode", + variant="primary", + size="lg", + ) + gr.Markdown( + "_Optimized for deep research with Claude Sonnet_", + elem_classes=["preset-description"], + ) + + automation_btn = gr.Button( + "🤖 Load Automation Mode", + variant="secondary", + size="lg", + ) + gr.Markdown( + "_Fast automation with GPT-4o_", + elem_classes=["preset-description"], + ) + + custom_browser_btn = gr.Button( + "🌐 Load Custom Browser Mode", + variant="secondary", + size="lg", + ) + gr.Markdown( + "_Use your Chrome profile for authenticated tasks_", + elem_classes=["preset-description"], + ) + + preset_status = gr.Markdown( + "", + visible=False, + elem_classes=["preset-status"], + ) + + # Right column: Status and Info + with gr.Column(scale=2): + gr.Markdown("### ℹ️ Configuration Status") + + status_display = gr.Markdown( + get_current_config_status(), + elem_classes=["status-display"], + ) + + refresh_status_btn = gr.Button( + "🔄 Refresh Status", + size="sm", + variant="secondary", + ) + + gr.Markdown("### 🎯 Common Use Cases") + + with gr.Row(): + with gr.Column(): + gr.Markdown( + """ + **🔍 Web Research** + - Use Deep Research agent in Agent Marketplace + - Enable MCP servers for extended capabilities + - Recommended: GPT-4 or Claude Sonnet + - Higher temperature (0.7-0.8) for creativity + """ + ) + with gr.Column(): + gr.Markdown( + """ + **🤖 Browser Automation** + - Use standard Run Agent tab + - Configure custom browser if accessing authenticated sites + - Enable vision for better element detection + - Lower temperature (0.5-0.6) for consistency + """ + ) + + gr.Markdown("### 📚 Getting Started Guide") + with gr.Accordion("📖 Quick Setup Instructions", open=False): + gr.Markdown( + """ + #### First Time Setup: + + 1. **Configure API Keys** (if not in .env) + - Go to Settings > Agent Settings + - Select your LLM provider + - Add API key if needed + + 2. **Choose Your Mode** + - Click a preset button above to auto-configure + - OR manually configure in Settings tab + + 3. **Run Your First Task** + - Go to "Run Agent" tab + - Enter your task description + - Click "Run Agent" and watch the magic happen! + + #### Tips: + + - **Vision Mode**: Enable for better screenshot understanding + - **Custom Browser**: Use your Chrome profile to access logged-in sites + - **MCP Servers**: Add filesystem, fetch, or brave-search for extended capabilities + - **Max Steps**: Increase for complex multi-step tasks + - **Save Configs**: Use "Config Management" tab to save your favorite setups + """ + ) + + # Register components + tab_components.update( + { + "research_btn": research_btn, + "automation_btn": automation_btn, + "custom_browser_btn": custom_browser_btn, + "preset_status": preset_status, + "status_display": status_display, + "refresh_status_btn": refresh_status_btn, + } + ) + + webui_manager.add_components("quick_start", tab_components) + + # Connect preset buttons + def load_research_preset(): + """Load research preset configuration.""" + updates = load_preset_config("research", webui_manager) + status_msg = """ +✅ **Research Mode Loaded!** + +Settings applied: +- LLM: Claude 3.5 Sonnet +- Temperature: 0.7 (creative) +- Vision: Enabled +- Max Steps: 150 + +Go to the **Settings** tab to review or adjust these settings. +""" + return [gr.update(value=val) for _, val in updates] + [ + gr.update(value=status_msg, visible=True) + ] + + def load_automation_preset(): + """Load automation preset configuration.""" + updates = load_preset_config("automation", webui_manager) + status_msg = """ +✅ **Automation Mode Loaded!** + +Settings applied: +- LLM: GPT-4o +- Temperature: 0.6 (balanced) +- Vision: Enabled +- Max Steps: 100 + +Go to the **Settings** tab to review or adjust these settings. +""" + return [gr.update(value=val) for _, val in updates] + [ + gr.update(value=status_msg, visible=True) + ] + + def load_custom_browser_preset(): + """Load custom browser preset configuration.""" + updates = load_preset_config("custom_browser", webui_manager) + status_msg = """ +✅ **Custom Browser Mode Loaded!** + +Settings applied: +- LLM: GPT-4o Mini (cost-effective) +- Use Own Browser: Enabled +- Vision: Enabled + +⚠️ **Important:** Close all Chrome windows before running the agent! + +Configure your Chrome path in the **Settings > Browser Settings** tab. +""" + return [gr.update(value=val) for _, val in updates] + [ + gr.update(value=status_msg, visible=True) + ] + + def refresh_status(): + """Refresh the status display.""" + return gr.update(value=get_current_config_status()) + + # Wire up button clicks + research_btn.click( + fn=load_research_preset, + inputs=[], + outputs=[ + webui_manager.get_component_by_id("agent_settings.llm_provider"), + webui_manager.get_component_by_id("agent_settings.llm_model_name"), + webui_manager.get_component_by_id("agent_settings.llm_temperature"), + webui_manager.get_component_by_id("agent_settings.use_vision"), + webui_manager.get_component_by_id("agent_settings.max_steps"), + webui_manager.get_component_by_id("agent_settings.max_actions"), + webui_manager.get_component_by_id("browser_settings.headless"), + webui_manager.get_component_by_id("browser_settings.keep_browser_open"), + preset_status, + ], + ) + + automation_btn.click( + fn=load_automation_preset, + inputs=[], + outputs=[ + webui_manager.get_component_by_id("agent_settings.llm_provider"), + webui_manager.get_component_by_id("agent_settings.llm_model_name"), + webui_manager.get_component_by_id("agent_settings.llm_temperature"), + webui_manager.get_component_by_id("agent_settings.use_vision"), + webui_manager.get_component_by_id("agent_settings.max_steps"), + webui_manager.get_component_by_id("agent_settings.max_actions"), + webui_manager.get_component_by_id("browser_settings.headless"), + webui_manager.get_component_by_id("browser_settings.keep_browser_open"), + preset_status, + ], + ) + + custom_browser_btn.click( + fn=load_custom_browser_preset, + inputs=[], + outputs=[ + webui_manager.get_component_by_id("agent_settings.llm_provider"), + webui_manager.get_component_by_id("agent_settings.llm_model_name"), + webui_manager.get_component_by_id("agent_settings.llm_temperature"), + webui_manager.get_component_by_id("agent_settings.use_vision"), + webui_manager.get_component_by_id("agent_settings.max_steps"), + webui_manager.get_component_by_id("agent_settings.max_actions"), + webui_manager.get_component_by_id("browser_settings.headless"), + webui_manager.get_component_by_id("browser_settings.keep_browser_open"), + webui_manager.get_component_by_id("browser_settings.use_own_browser"), + preset_status, + ], + ) + + refresh_status_btn.click( + fn=refresh_status, + inputs=[], + outputs=[status_display], + ) diff --git a/src/web_ui/webui/components/workflow_visualizer.py b/src/web_ui/webui/components/workflow_visualizer.py new file mode 100644 index 00000000..f5d4e3fd --- /dev/null +++ b/src/web_ui/webui/components/workflow_visualizer.py @@ -0,0 +1,187 @@ +""" +Workflow visualization component for Gradio UI. +""" + +from typing import Any + +import gradio as gr + + +def create_workflow_visualizer() -> tuple[gr.JSON, gr.Markdown]: + """ + Create a simple workflow visualizer using Gradio's built-in components. + + Returns a tuple of (JSON component for graph data, Markdown component for current status). + + Note: This is a simplified version using JSON display. For production, + consider creating a custom Gradio component with React Flow. + """ + + # Workflow graph data display + workflow_json = gr.JSON( + label="Workflow Graph", + elem_id="workflow_graph", + ) + + # Current step status + workflow_status = gr.Markdown(value="**Status:** Ready to start", elem_id="workflow_status") + + return workflow_json, workflow_status + + +def format_workflow_for_display(workflow_data: dict[str, Any]) -> dict[str, Any]: + """ + Format workflow data for better readability in JSON display. + + Args: + workflow_data: Raw workflow data from WorkflowGraphBuilder + + Returns: + Formatted workflow data optimized for display + """ + if not workflow_data: + return {"message": "No workflow data available"} + + # Create a more readable structure + formatted: dict[str, Any] = { + "summary": { + "total_nodes": workflow_data.get("metadata", {}).get("total_nodes", 0), + "total_edges": workflow_data.get("metadata", {}).get("total_edges", 0), + "depth": workflow_data.get("metadata", {}).get("depth", 0), + }, + "steps": [], + } + + # Convert nodes to a timeline-style format + nodes = workflow_data.get("nodes", []) + for node in nodes: + node_data = node.get("data", {}) + step = { + "id": node.get("id"), + "type": node.get("type"), + "label": node_data.get("label"), + "status": node_data.get("status"), + "icon": node_data.get("icon", "⚡"), + } + + # Add duration if available + if "duration" in node_data: + step["duration_ms"] = node_data["duration"] + + # Add type-specific details + if node.get("type") == "action": + step["action"] = node_data.get("action") + step["params"] = node_data.get("params", {}) + elif node.get("type") == "thinking": + step["content"] = node_data.get("content") + elif node.get("type") in ("result", "error"): + step["result"] = node_data.get("result") or node_data.get("error") + + steps = formatted.get("steps") + if isinstance(steps, list): + steps.append(step) + + return formatted + + +def generate_workflow_status_markdown(workflow_data: dict[str, Any]) -> str: + """ + Generate a Markdown status summary from workflow data. + + Args: + workflow_data: Raw workflow data from WorkflowGraphBuilder + + Returns: + Markdown-formatted status string + """ + if not workflow_data or not workflow_data.get("nodes"): + return "**Status:** No workflow data available" + + nodes = workflow_data.get("nodes", []) + metadata = workflow_data.get("metadata", {}) + + # Find current (last) node + current_node = nodes[-1] if nodes else None + + if not current_node: + return "**Status:** Ready to start" + + node_data = current_node.get("data", {}) + status = node_data.get("status", "unknown") + label = node_data.get("label", "Step") + # icon is not currently used but kept for future extensibility + _ = node_data.get("icon", "⚡") + + # Build status message + status_emoji = { + "pending": "⏳", + "running": "▶️", + "completed": "✅", + "error": "❌", + "skipped": "⏭️", + } + + status_icon = status_emoji.get(status, "•") + + message = f"{status_icon} **{label}**" + + # Add details based on node type + if current_node.get("type") == "action": + action = node_data.get("action", "") + message += f" - {action}" + elif current_node.get("type") == "thinking": + content = node_data.get("content", "")[:50] + message += f" - {content}..." + + # Add progress + total_nodes = metadata.get("total_nodes", 0) + current_index = len(nodes) + message += f"\n\n**Progress:** {current_index}/{total_nodes} steps" + + # Add duration if completed + if status == "completed" and "duration" in node_data: + duration = node_data["duration"] + message += f" | Duration: {duration:.0f}ms" + + return message + + +# CSS for workflow visualization +WORKFLOW_CSS = """ +/* Workflow visualization styling */ +#workflow_graph { + max-height: 600px; + overflow-y: auto; +} + +#workflow_status { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 16px 20px; + border-radius: 8px; + margin: 12px 0; + box-shadow: 0 4px 6px rgba(0,0,0,0.1); +} + +#workflow_status strong { + font-size: 1.1em; +} + +/* Make JSON display more readable */ +#workflow_graph .json-node { + margin: 4px 0; +} + +#workflow_graph .json-key { + color: #667eea; + font-weight: 600; +} + +#workflow_graph .json-string { + color: #22863a; +} + +#workflow_graph .json-number { + color: #005cc5; +} +""" diff --git a/src/web_ui/webui/interface.py b/src/web_ui/webui/interface.py new file mode 100644 index 00000000..9d6aee6b --- /dev/null +++ b/src/web_ui/webui/interface.py @@ -0,0 +1,802 @@ +import gradio as gr + +from src.web_ui.webui.components.browser_use_agent_tab import ( + handle_clear, + handle_pause_resume, + handle_stop, + handle_submit, + run_agent_task, +) +from src.web_ui.webui.components.dashboard_main import create_dashboard_main +from src.web_ui.webui.components.dashboard_settings import create_dashboard_settings +from src.web_ui.webui.components.dashboard_sidebar import create_dashboard_sidebar +from src.web_ui.webui.components.deep_research_agent_tab import ( + run_deep_research, + stop_deep_research, +) +from src.web_ui.webui.components.help_modal import create_help_modal +from src.web_ui.webui.components.mcp_settings_tab import create_mcp_settings_tab +from src.web_ui.webui.webui_manager import WebuiManager + +theme_map = { + "Default": gr.themes.Default(), + "Soft": gr.themes.Soft(), + "Monochrome": gr.themes.Monochrome(), + "Glass": gr.themes.Glass(), + "Origin": gr.themes.Origin(), + "Citrus": gr.themes.Citrus(), + "Ocean": gr.themes.Ocean(), + "Base": gr.themes.Base(), +} + + +def create_ui(theme_name="Ocean"): + css = """ + .gradio-container { + width: 95vw !important; + max-width: 95% !important; + margin-left: auto !important; + margin-right: auto !important; + padding-top: 10px !important; + } + + /* Header Styles */ + .header-container { + text-align: center; + padding: 20px; + background: linear-gradient(135deg, rgba(99, 102, 241, 0.12), rgba(168, 85, 247, 0.12)); + border-radius: 12px; + margin-bottom: 16px; + display: flex; + justify-content: space-between; + align-items: center; + } + .header-left { + flex: 1; + } + .header-center { + flex: 2; + text-align: center; + } + .header-right { + flex: 1; + text-align: right; + } + .header-title { + margin: 0; + font-size: 1.8em; + font-weight: 700; + background: linear-gradient(135deg, #6366f1, #a855f7); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } + .header-tagline { + font-size: 0.95em; + opacity: 0.8; + margin-top: 4px; + } + + /* Dashboard Layout */ + .dashboard-container { + display: flex; + gap: 16px; + min-height: calc(100vh - 250px); + } + + .dashboard-sidebar { + width: 250px; + min-width: 250px; + border-right: 1px solid rgba(128, 128, 128, 0.2); + padding-right: 16px; + overflow-y: auto; + } + + .dashboard-main { + flex: 1; + overflow-y: auto; + padding: 0 16px; + } + + .dashboard-settings { + width: 400px; + min-width: 400px; + max-width: 400px; + overflow-y: auto; + border-left: 1px solid rgba(128, 128, 128, 0.2); + padding-left: 16px; + } + + /* Status Cards */ + .status-card { + background: rgba(99, 102, 241, 0.05); + border: 1px solid rgba(99, 102, 241, 0.2); + border-radius: 8px; + padding: 12px; + margin-bottom: 12px; + } + .status-card h3 { + margin-top: 0; + font-size: 1.1em; + margin-bottom: 12px; + } + + /* Preset Buttons */ + .preset-button-group { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 16px; + } + .preset-button { + width: 100%; + text-align: left !important; + } + + /* History List */ + .history-list { + max-height: 200px; + overflow-y: auto; + } + .history-item { + padding: 8px; + border-left: 2px solid rgba(99, 102, 241, 0.3); + margin-bottom: 8px; + font-size: 0.9em; + cursor: pointer; + background: rgba(255, 255, 255, 0.3); + border-radius: 4px; + } + .history-item:hover { + background: rgba(99, 102, 241, 0.1); + } + + + /* Help Modal */ + .help-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; + } + .help-modal-content { + background: var(--body-background-fill); + padding: 30px; + border-radius: 12px; + max-width: 800px; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + } + + /* MCP Settings Modal */ + .mcp-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + } + .mcp-modal-content { + background: var(--body-background-fill); + padding: 30px; + border-radius: 12px; + width: 90%; + max-width: 1000px; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + } + + /* Agent Selector */ + .agent-selector { + margin-bottom: 16px; + } + + /* Loading States */ + .loading-spinner { + border: 4px solid rgba(99, 102, 241, 0.1); + border-top: 4px solid #6366f1; + border-radius: 50%; + width: 40px; + height: 40px; + animation: spin 1s linear infinite; + } + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + + /* Notification System */ + #notification-container { + position: fixed; + top: 20px; + right: 20px; + z-index: 9999; + display: flex; + flex-direction: column; + gap: 10px; + max-width: 400px; + } + .notification { + display: flex; + align-items: flex-start; + gap: 12px; + padding: 16px; + background: white; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + animation: slideIn 0.3s forwards; + } + @keyframes slideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + /* Improved button styles */ + .gr-button { + border-radius: 6px; + font-weight: 500; + transition: all 0.2s; + } + .gr-button-primary { + background: linear-gradient(135deg, #6366f1, #a855f7) !important; + border: none !important; + } + .gr-button-secondary { + border: 1px solid rgba(99, 102, 241, 0.3) !important; + } + + /* Desktop-first responsiveness */ + @media (max-width: 1400px) { + .dashboard-settings { + width: 350px; + min-width: 350px; + max-width: 350px; + } + } + + @media (max-width: 1200px) { + .dashboard-sidebar { + width: 220px; + min-width: 220px; + } + } + """ + + # Enhanced JavaScript features + js_func = """ + function refresh() { + const url = new URL(window.location); + if (url.searchParams.get('__theme') !== 'dark') { + url.searchParams.set('__theme', 'dark'); + window.location.href = url.href; + } + } + + // Initialize features after Gradio is ready + setTimeout(function() { + // Keyboard shortcuts + document.addEventListener('keydown', function(e) { + // Ctrl/Cmd + Enter to submit + if ((e.ctrlKey || e.metaKey) && e.key === 'Enter' && e.target.matches('textarea')) { + const runButton = document.querySelector('button[id*="run"]'); + if (runButton) runButton.click(); + } + + // Escape to stop + if (e.key === 'Escape' && !e.target.matches('input, textarea')) { + const stopButton = document.querySelector('button[id*="stop"]'); + if (stopButton) stopButton.click(); + } + + // ? to show help + if (e.key === '?' && !e.target.matches('input, textarea')) { + const helpButton = document.querySelector('button[id*="help"]'); + if (helpButton) helpButton.click(); + } + }); + + // Notification system + window.showNotification = function(type, title, message, duration) { + duration = duration || 5000; + let container = document.getElementById('notification-container'); + if (!container) { + container = document.createElement('div'); + container.id = 'notification-container'; + document.body.appendChild(container); + } + + const icons = { + success: '✓', + info: 'ℹ', + warning: '⚠', + error: '✕' + }; + + const notification = document.createElement('div'); + notification.className = 'notification notification-' + type; + notification.innerHTML = ` +
${icons[type] || 'ℹ'}
+
+ ${title} +

${message}

+
+ + `; + container.appendChild(notification); + + setTimeout(function() { + if (notification.parentNode) notification.remove(); + }, duration); + }; + }, 100); + """ + + ui_manager = WebuiManager() + + with gr.Blocks( + title="Browser Use WebUI", + theme=theme_map[theme_name], + css=css, + js=js_func, + ) as demo: + # Header with Help button + with gr.Row(elem_classes=["header-container"]): + gr.HTML("
") + gr.HTML( + """ +
+

🌐 Browser Use WebUI

+

AI-Powered Browser Automation Platform

+
+ """ + ) + with gr.Column(elem_classes=["header-right"]): + help_button = gr.Button("❓ Help", size="sm", variant="secondary") + + # Main Dashboard Layout + with gr.Row(elem_classes=["dashboard-container"]): + # Left Sidebar + with gr.Column(elem_classes=["dashboard-sidebar"], scale=0): + create_dashboard_sidebar(ui_manager) + + # Main Content Area + with gr.Column(elem_classes=["dashboard-main"], scale=3): + create_dashboard_main(ui_manager) + + # Settings Panel - Always visible + with gr.Column( + elem_classes=["dashboard-settings"], + scale=0, + visible=True, + ): + create_dashboard_settings(ui_manager) + + # Help Modal (overlay) + create_help_modal(ui_manager) + + # MCP Settings Modal (overlay) + with gr.Group(visible=False, elem_classes=["mcp-modal-overlay"]) as mcp_modal: + with gr.Column(elem_classes=["mcp-modal-content"]): + create_mcp_settings_tab(ui_manager) + close_mcp_button = gr.Button("Close", variant="primary", size="lg") + + # Wire up Help Modal + def show_help(): + """Show help modal.""" + _ = ui_manager.get_component_by_id("help_modal.help_modal") + return gr.update(visible=True) + + def hide_help(): + """Hide help modal.""" + return gr.update(visible=False) + + help_button.click( + fn=show_help, + inputs=[], + outputs=[ui_manager.get_component_by_id("help_modal.help_modal")], + ) + + # Wire up MCP Modal + def show_mcp_modal(): + """Show MCP settings modal.""" + return gr.update(visible=True) + + def hide_mcp_modal(): + """Hide MCP settings modal.""" + return gr.update(visible=False) + + edit_mcp_btn = ui_manager.get_component_by_id("dashboard_settings.edit_mcp_button") + edit_mcp_btn.click( # type: ignore[attr-defined] + fn=show_mcp_modal, + inputs=[], + outputs=[mcp_modal], + ) + + close_mcp_button.click( + fn=hide_mcp_modal, + inputs=[], + outputs=[mcp_modal], + ) + + # Wire up Settings Panel Event Handlers AFTER all components are registered + # This ensures Gradio's event system initializes properly + from src.web_ui.webui.components.dashboard_settings import update_model_dropdown + + # Get component references + llm_provider_comp = ui_manager.get_component_by_id("dashboard_settings.llm_provider") + llm_model_comp = ui_manager.get_component_by_id("dashboard_settings.llm_model_name") + ollama_ctx_comp = ui_manager.get_component_by_id("dashboard_settings.ollama_num_ctx") + use_planner_comp = ui_manager.get_component_by_id("dashboard_settings.use_planner") + planner_group_comp = ui_manager.get_component_by_id("dashboard_settings.planner_group") + planner_llm_provider_comp = ui_manager.get_component_by_id( + "dashboard_settings.planner_llm_provider" + ) + planner_llm_model_comp = ui_manager.get_component_by_id( + "dashboard_settings.planner_llm_model_name" + ) + planner_ollama_ctx_comp = ui_manager.get_component_by_id( + "dashboard_settings.planner_ollama_num_ctx" + ) + use_own_browser_comp = ui_manager.get_component_by_id("dashboard_settings.use_own_browser") + custom_browser_group_comp = ui_manager.get_component_by_id( + "dashboard_settings.custom_browser_group" + ) + headless_comp = ui_manager.get_component_by_id("dashboard_settings.headless") + keep_browser_open_comp = ui_manager.get_component_by_id( + "dashboard_settings.keep_browser_open" + ) + disable_security_comp = ui_manager.get_component_by_id( + "dashboard_settings.disable_security" + ) + + # LLM Provider change -> Update model dropdown and show/hide Ollama context + def update_llm_settings(provider): + """Update both model dropdown and Ollama context visibility.""" + print("="*60) + print(f"[DEBUG] ⚡ update_llm_settings CALLED with provider: {provider}") + print(f"[DEBUG] Provider type: {type(provider)}") + print("="*60) + + models_update = update_model_dropdown(provider) + ollama_visible = gr.update(visible=provider == "ollama") + + print(f"[DEBUG] ✅ Model update: {models_update}") + print(f"[DEBUG] ✅ Ollama visible: {ollama_visible}") + print(f"[DEBUG] Returning {len(models_update.get('choices', []))} model choices") + print("="*60) + + return models_update, ollama_visible + + print("[SETUP] Attaching .change() handler to llm_provider_comp...") + print(f"[SETUP] llm_provider_comp type: {type(llm_provider_comp)}") + print(f"[SETUP] llm_provider_comp value: {getattr(llm_provider_comp, 'value', 'NO VALUE')}") + + change_event = llm_provider_comp.change( # type: ignore[attr-defined] + fn=update_llm_settings, + inputs=[llm_provider_comp], + outputs=[llm_model_comp, ollama_ctx_comp], + ) + + print(f"[SETUP] ✅ Change handler attached: {change_event}") + print("[SETUP] Change handler should now fire when provider dropdown changes!") + + # Planner checkbox -> Show/hide planner group + use_planner_comp.change( # type: ignore[attr-defined] + fn=lambda checked: gr.update(visible=checked), + inputs=[use_planner_comp], + outputs=[planner_group_comp], + ) + + # Planner provider change + def update_planner_settings(provider): + """Update both planner model dropdown and Ollama context visibility.""" + print(f"[DEBUG] ⚡ Planner provider changed to: {provider}") + models_update = update_model_dropdown(provider) + ollama_visible = gr.update(visible=provider == "ollama") + print(f"[DEBUG] ✅ Planner model update complete") + return models_update, ollama_visible + + planner_llm_provider_comp.change( # type: ignore[attr-defined] + fn=update_planner_settings, + inputs=[planner_llm_provider_comp], + outputs=[planner_llm_model_comp, planner_ollama_ctx_comp], + ) + + # Use Own Browser checkbox -> Show/hide custom browser fields + use_own_browser_comp.change( # type: ignore[attr-defined] + fn=lambda checked: gr.update(visible=checked), + inputs=[use_own_browser_comp], + outputs=[custom_browser_group_comp], + ) + + # Browser config changes -> Close browser + from src.web_ui.webui.components.dashboard_settings import close_browser + + async def close_wrapper(): + """Wrapper for closing browser.""" + await close_browser(ui_manager) + + headless_comp.change(close_wrapper) # type: ignore[attr-defined] + keep_browser_open_comp.change(close_wrapper) # type: ignore[attr-defined] + disable_security_comp.change(close_wrapper) # type: ignore[attr-defined] + use_own_browser_comp.change(close_wrapper) # type: ignore[attr-defined] + + # Wire up Preset Buttons from Sidebar + # These will update settings in the Settings panel + research_btn = ui_manager.get_component_by_id("dashboard_sidebar.research_btn") + automation_btn = ui_manager.get_component_by_id("dashboard_sidebar.automation_btn") + custom_browser_btn = ui_manager.get_component_by_id("dashboard_sidebar.custom_browser_btn") + + def load_research_preset(): + """Load research preset configuration.""" + # Update model dropdown manually since .change() doesn't fire from .click() updates + model_update = update_model_dropdown("anthropic") + return [ + gr.update(value="anthropic"), # llm_provider + model_update, # llm_model_name - updated based on provider + gr.update(value=0.7), # llm_temperature + gr.update(value=True), # use_vision + gr.update(value=150), # max_steps + gr.update(value=10), # max_actions + gr.update(value=False), # headless + gr.update(value=True), # keep_browser_open + ] + + def load_automation_preset(): + """Load automation preset configuration.""" + # Update model dropdown manually since .change() doesn't fire from .click() updates + model_update = update_model_dropdown("openai") + return [ + gr.update(value="openai"), # llm_provider + model_update, # llm_model_name - updated based on provider + gr.update(value=0.6), + gr.update(value=True), + gr.update(value=100), + gr.update(value=10), + gr.update(value=False), + gr.update(value=True), + ] + + def load_custom_browser_preset(): + """Load custom browser preset configuration.""" + # Update model dropdown manually since .change() doesn't fire from .click() updates + model_update = update_model_dropdown("openai") + return [ + gr.update(value="openai"), # llm_provider + model_update, # llm_model_name - updated based on provider + gr.update(value=0.6), + gr.update(value=True), + gr.update(value=100), + gr.update(value=10), + gr.update(value=False), + gr.update(value=True), + gr.update(value=True), # use_own_browser + ] + + research_btn.click( # type: ignore[attr-defined] + fn=load_research_preset, + inputs=[], + outputs=[ + ui_manager.get_component_by_id("dashboard_settings.llm_provider"), + ui_manager.get_component_by_id("dashboard_settings.llm_model_name"), + ui_manager.get_component_by_id("dashboard_settings.llm_temperature"), + ui_manager.get_component_by_id("dashboard_settings.use_vision"), + ui_manager.get_component_by_id("dashboard_settings.max_steps"), + ui_manager.get_component_by_id("dashboard_settings.max_actions"), + ui_manager.get_component_by_id("dashboard_settings.headless"), + ui_manager.get_component_by_id("dashboard_settings.keep_browser_open"), + ], + ) + + automation_btn.click( # type: ignore[attr-defined] + fn=load_automation_preset, + inputs=[], + outputs=[ + ui_manager.get_component_by_id("dashboard_settings.llm_provider"), + ui_manager.get_component_by_id("dashboard_settings.llm_model_name"), + ui_manager.get_component_by_id("dashboard_settings.llm_temperature"), + ui_manager.get_component_by_id("dashboard_settings.use_vision"), + ui_manager.get_component_by_id("dashboard_settings.max_steps"), + ui_manager.get_component_by_id("dashboard_settings.max_actions"), + ui_manager.get_component_by_id("dashboard_settings.headless"), + ui_manager.get_component_by_id("dashboard_settings.keep_browser_open"), + ], + ) + + custom_browser_btn.click( # type: ignore[attr-defined] + fn=load_custom_browser_preset, + inputs=[], + outputs=[ + ui_manager.get_component_by_id("dashboard_settings.llm_provider"), + ui_manager.get_component_by_id("dashboard_settings.llm_model_name"), + ui_manager.get_component_by_id("dashboard_settings.llm_temperature"), + ui_manager.get_component_by_id("dashboard_settings.use_vision"), + ui_manager.get_component_by_id("dashboard_settings.max_steps"), + ui_manager.get_component_by_id("dashboard_settings.max_actions"), + ui_manager.get_component_by_id("dashboard_settings.headless"), + ui_manager.get_component_by_id("dashboard_settings.keep_browser_open"), + ui_manager.get_component_by_id("dashboard_settings.use_own_browser"), + ], + ) + + # Wire up Save/Load Config + save_config_btn = ui_manager.get_component_by_id("dashboard_settings.save_config_button") + save_default_btn = ui_manager.get_component_by_id("dashboard_settings.save_default_button") + load_config_btn = ui_manager.get_component_by_id("dashboard_settings.load_config_button") + config_file = ui_manager.get_component_by_id("dashboard_settings.config_file") + config_status = ui_manager.get_component_by_id("dashboard_settings.config_status") + + save_config_btn.click( # type: ignore[attr-defined] + fn=ui_manager.save_config, + inputs=list(ui_manager.get_components()), + outputs=[config_status], + ) + + def save_default_wrapper(*args): + """Wrapper for save_as_default that returns status message.""" + file_path = ui_manager.save_as_default(*args) + return gr.update(value=f"✅ Saved as default settings: {file_path}") + + save_default_btn.click( # type: ignore[attr-defined] + fn=save_default_wrapper, + inputs=list(ui_manager.get_components()), + outputs=[config_status], + ) + + load_config_btn.click( # type: ignore[attr-defined] + fn=lambda: gr.update(visible=True), + inputs=[], + outputs=[config_file], + ) + + config_file.change( # type: ignore[attr-defined] + fn=ui_manager.load_config, + inputs=[config_file], + outputs=ui_manager.get_components(), + ) + + # Initialize default settings and migrate old settings + from src.web_ui.utils.config import DEFAULT_SETTINGS_FILE + + # Migrate old settings + migrated_count = ui_manager.migrate_old_settings() + if migrated_count > 0: + print(f"✅ Migrated {migrated_count} settings files to data/saved_configs/") + + # Load default settings + default_loaded = ui_manager.load_default_settings() + if default_loaded: + print(f"✅ Loaded default settings from {DEFAULT_SETTINGS_FILE}") + else: + print("ℹ️ No default settings found, using environment defaults") + + # Initialize Browser Use Agent + ui_manager.init_browser_use_agent() + + # Wire up Browser Use Agent handlers + run_button = ui_manager.get_component_by_id("browser_use_agent.run_button") + stop_button = ui_manager.get_component_by_id("browser_use_agent.stop_button") + pause_resume_button = ui_manager.get_component_by_id( + "browser_use_agent.pause_resume_button" + ) + clear_button = ui_manager.get_component_by_id("browser_use_agent.clear_button") + submit_help_button = ui_manager.get_component_by_id("browser_use_agent.submit_help_button") + chatbot = ui_manager.get_component_by_id("browser_use_agent.chatbot") + + # Wrapper functions to handle async generator functions + async def run_agent_wrapper(*args): + """Wrapper for run_agent_task that yields updates.""" + components_dict = dict(zip(ui_manager.get_components(), args, strict=True)) + async for update_dict in run_agent_task(ui_manager, components_dict): + yield list(update_dict.values()) + + async def stop_wrapper(): + """Wrapper for handle_stop.""" + await handle_stop(ui_manager) + return [] + + async def pause_resume_wrapper(): + """Wrapper for handle_pause_resume.""" + result = await handle_pause_resume(ui_manager) + return [result] + + async def clear_wrapper(): + """Wrapper for handle_clear.""" + result = await handle_clear(ui_manager) + return [result] + + async def submit_help_wrapper(*args): + """Wrapper for handle_submit.""" + components_dict = dict(zip(ui_manager.get_components(), args, strict=True)) + async for update_dict in handle_submit(ui_manager, components_dict): + yield list(update_dict.values()) + + run_button.click( # type: ignore[attr-defined] + fn=run_agent_wrapper, + inputs=ui_manager.get_components(), + outputs=ui_manager.get_components(), + ) + + stop_button.click( # type: ignore[attr-defined] + fn=stop_wrapper, + inputs=[], + outputs=[], + ) + + pause_resume_button.click( # type: ignore[attr-defined] + fn=pause_resume_wrapper, + inputs=[], + outputs=[pause_resume_button], + ) + + clear_button.click( # type: ignore[attr-defined] + fn=clear_wrapper, + inputs=[], + outputs=[chatbot], + ) + + submit_help_button.click( # type: ignore[attr-defined] + fn=submit_help_wrapper, + inputs=ui_manager.get_components(), + outputs=ui_manager.get_components(), + ) + + # Initialize Deep Research Agent + ui_manager.init_deep_research_agent() + + # Wire up Deep Research Agent handlers + start_button = ui_manager.get_component_by_id("deep_research_agent.start_button") + stop_button_dr = ui_manager.get_component_by_id("deep_research_agent.stop_button") + clear_button_dr = ui_manager.get_component_by_id("deep_research_agent.clear_button") + markdown_display = ui_manager.get_component_by_id("deep_research_agent.markdown_display") + + # Wrapper functions for Deep Research Agent + async def run_research_wrapper(*args): + """Wrapper for run_deep_research that yields updates.""" + components_dict = dict(zip(ui_manager.get_components(), args, strict=True)) + async for update_dict in run_deep_research(ui_manager, components_dict): + yield list(update_dict.values()) + + async def stop_research_wrapper(): + """Wrapper for stop_deep_research.""" + result = await stop_deep_research(ui_manager) + return list(result.values()) if result else [] + + start_button.click( # type: ignore[attr-defined] + fn=run_research_wrapper, + inputs=ui_manager.get_components(), + outputs=ui_manager.get_components(), + ) + + stop_button_dr.click( # type: ignore[attr-defined] + fn=stop_research_wrapper, + inputs=[], + outputs=[], + ) + + clear_button_dr.click( # type: ignore[attr-defined] + fn=lambda: gr.update(value="Ready to start new research..."), + inputs=[], + outputs=[markdown_display], + ) + + return demo diff --git a/src/web_ui/webui/webui_manager.py b/src/web_ui/webui/webui_manager.py new file mode 100644 index 00000000..23820571 --- /dev/null +++ b/src/web_ui/webui/webui_manager.py @@ -0,0 +1,320 @@ +import asyncio +import json +import os +import shutil +import time +from datetime import datetime + +import gradio as gr +from browser_use.agent.service import Agent +from gradio.components import Component + +from src.web_ui.agent.deep_research.deep_research_agent import DeepResearchAgent +from src.web_ui.browser.custom_browser import CustomBrowser +from src.web_ui.browser.custom_context import CustomBrowserContext +from src.web_ui.controller.custom_controller import CustomController +from src.web_ui.utils.config import ( + DEFAULT_SETTINGS_FILE, + OLD_SETTINGS_DIR, + SETTINGS_ARCHIVE_DIR, + ensure_settings_directories, + is_runtime_component, +) + + +class WebuiManager: + def __init__(self, settings_save_dir: str = SETTINGS_ARCHIVE_DIR): + self.id_to_component: dict[str, Component] = {} + self.component_to_id: dict[Component, str] = {} + + self.settings_save_dir = settings_save_dir + ensure_settings_directories() + + # Dashboard state management + self.settings_panel_visible: bool = False + self.current_agent_type: str = "browser_use" # "browser_use" or "deep_research" + self.recent_tasks: list[dict] = [] # List of recent task executions + self.token_usage: dict = {"used": 0, "cost": 0.0} # Token usage tracking + + def init_browser_use_agent(self) -> None: + """ + init browser use agent + """ + self.bu_agent: Agent | None = None + self.bu_browser: CustomBrowser | None = None + self.bu_browser_context: CustomBrowserContext | None = None + self.bu_controller: CustomController | None = None + self.bu_chat_history: list[dict[str, str | None]] = [] + self.bu_response_event: asyncio.Event | None = None + self.bu_user_help_response: str | None = None + self.bu_current_task: asyncio.Task | None = None + self.bu_agent_task_id: str | None = None + + def init_deep_research_agent(self) -> None: + """ + init deep research agent + """ + self.dr_agent: DeepResearchAgent | None = None + self.dr_current_task = None + self.dr_agent_task_id: str | None = None + self.dr_task_id: str | None = None + self.dr_save_dir: str | None = None + + def add_components(self, tab_name: str, components_dict: dict[str, Component]) -> None: + """ + Add tab components + """ + for comp_name, component in components_dict.items(): + comp_id = f"{tab_name}.{comp_name}" + self.id_to_component[comp_id] = component + self.component_to_id[component] = comp_id + + def get_components(self) -> list[Component]: + """ + Get all components + """ + return list(self.id_to_component.values()) + + def get_component_by_id(self, comp_id: str) -> Component: + """ + Get component by id + """ + return self.id_to_component[comp_id] + + def get_id_by_component(self, comp: Component) -> str: + """ + Get id by component + """ + return self.component_to_id[comp] + + def save_config(self, *args, as_default: bool = False) -> str: + """ + Save config to timestamped file or as default. + + Args: + *args: Component values + as_default: If True, save as default_settings.json instead of timestamped file + + Returns: + Path to saved config file + """ + # Convert args to components dict + components = {} + all_components = list(self.id_to_component.values()) + for i, comp in enumerate(all_components): + if i < len(args): + components[comp] = args[i] + + cur_settings = {} + for comp in components: + if ( + not isinstance(comp, gr.Button) + and not isinstance(comp, gr.File) + and str(getattr(comp, "interactive", True)).lower() != "false" + ): + comp_id = self.get_id_by_component(comp) + # Filter out runtime-only components + if not is_runtime_component(comp_id): + cur_settings[comp_id] = components[comp] + + if as_default: + config_path = DEFAULT_SETTINGS_FILE + else: + config_name = datetime.now().strftime("%Y%m%d-%H%M%S") + config_path = os.path.join(self.settings_save_dir, f"{config_name}.json") + + with open(config_path, "w") as fw: + json.dump(cur_settings, fw, indent=4) + + return config_path + + def load_config(self, config_path: str): + """ + Load config + """ + with open(config_path) as fr: + ui_settings = json.load(fr) + + update_components = {} + for comp_id, comp_val in ui_settings.items(): + if comp_id in self.id_to_component: + comp = self.id_to_component[comp_id] + if comp.__class__.__name__ == "Chatbot": + update_components[comp] = comp.__class__(value=comp_val, type="messages") + else: + update_components[comp] = comp.__class__(value=comp_val) + if comp_id == "agent_settings.planner_llm_provider": + yield update_components # yield provider, let callback run + time.sleep(0.1) # wait for Gradio UI callback + + config_status = self.id_to_component["load_save_config.config_status"] + update_components.update( + { + config_status: config_status.__class__( + value=f"Successfully loaded config: {config_path}" + ) + } + ) + yield update_components + + def load_default_settings(self) -> bool: + """ + Load default settings if they exist. + + Returns: + True if default settings were loaded, False otherwise + """ + if os.path.exists(DEFAULT_SETTINGS_FILE): + try: + # Load default settings without showing status message + with open(DEFAULT_SETTINGS_FILE) as fr: + ui_settings = json.load(fr) + + update_components = {} + provider_changed = False + provider_value = None + + for comp_id, comp_val in ui_settings.items(): + if comp_id in self.id_to_component: + comp = self.id_to_component[comp_id] + if comp.__class__.__name__ == "Chatbot": + update_components[comp] = comp.__class__( + value=comp_val, type="messages" + ) + else: + update_components[comp] = comp.__class__(value=comp_val) + + # Track if provider changed to trigger model dropdown update + if "llm_provider" in comp_id: + provider_changed = True + provider_value = comp_val + + # Apply updates without yielding (blocking update) + for comp, val in update_components.items(): + comp.value = val + + # Manually trigger provider change to update model dropdown + if provider_changed and provider_value: + try: + # Import here to avoid circular dependencies + from src.web_ui.webui.components.dashboard_settings import ( + update_model_dropdown, + ) + + # Update model dropdown for the provider + model_update = update_model_dropdown(provider_value) + + # Find and update the model component + model_comp_id = comp_id.replace("llm_provider", "llm_model_name") + if model_comp_id in self.id_to_component: + model_comp = self.id_to_component[model_comp_id] + model_comp.value = model_update.get("value", "") + # Also update choices if available + if "choices" in model_update: + model_comp.choices = model_update["choices"] + + except Exception as e: + print(f"Warning: Could not trigger model dropdown update: {e}") + + return True + except Exception as e: + print(f"Failed to load default settings: {e}") + return False + return False + + def save_as_default(self, *args) -> str: + """ + Save current settings as default. + + Args: + *args: Component values + + Returns: + Path to saved default settings file + """ + return self.save_config(*args, as_default=True) + + def migrate_old_settings(self) -> int: + """ + Migrate old settings from tmp/webui_settings to data/saved_configs. + + Returns: + Number of files migrated + """ + migrated_count = 0 + if os.path.exists(OLD_SETTINGS_DIR): + try: + for filename in os.listdir(OLD_SETTINGS_DIR): + if filename.endswith(".json"): + src = os.path.join(OLD_SETTINGS_DIR, filename) + dst = os.path.join(self.settings_save_dir, filename) + shutil.copy2(src, dst) + migrated_count += 1 + print(f"Migrated {migrated_count} settings files from {OLD_SETTINGS_DIR}") + except Exception as e: + print(f"Failed to migrate old settings: {e}") + return migrated_count + + def toggle_settings_panel(self) -> bool: + """ + Toggle the settings panel visibility. + + Returns: + bool: New visibility state + """ + self.settings_panel_visible = not self.settings_panel_visible + return self.settings_panel_visible + + def add_recent_task(self, task: str, success: bool = True, result: str | None = None) -> None: + """ + Add a task to recent tasks history. + + Args: + task: Task description + success: Whether the task completed successfully + result: Optional result summary + """ + from datetime import datetime + + task_entry = { + "task": task, + "success": success, + "result": result, + "timestamp": datetime.now().strftime("%H:%M:%S"), + } + + self.recent_tasks.append(task_entry) + + # Keep only last 20 tasks + if len(self.recent_tasks) > 20: + self.recent_tasks = self.recent_tasks[-20:] + + def update_token_usage(self, tokens: int, cost: float = 0.0) -> None: + """ + Update token usage statistics. + + Args: + tokens: Number of tokens used + cost: Estimated cost in USD + """ + self.token_usage["used"] += tokens + self.token_usage["cost"] += cost + + def reset_token_usage(self) -> None: + """Reset token usage statistics.""" + self.token_usage = {"used": 0, "cost": 0.0} + + def get_status_summary(self) -> dict: + """ + Get a summary of current system status. + + Returns: + dict with status information + """ + return { + "settings_visible": self.settings_panel_visible, + "current_agent": self.current_agent_type, + "browser_open": bool(self.bu_browser), + "recent_task_count": len(self.recent_tasks), + "token_usage": self.token_usage, + } diff --git a/supervisord.conf b/supervisord.conf new file mode 100644 index 00000000..60107669 --- /dev/null +++ b/supervisord.conf @@ -0,0 +1,80 @@ +[supervisord] +user=root +nodaemon=true +logfile=/dev/stdout +logfile_maxbytes=0 +loglevel=error + +[program:xvfb] +command=Xvfb :99 -screen 0 %(ENV_RESOLUTION)s -ac +extension GLX +render -noreset +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +priority=100 +startsecs=3 +stopsignal=TERM +stopwaitsecs=10 + +[program:vnc_setup] +command=bash -c "mkdir -p ~/.vnc && echo '%(ENV_VNC_PASSWORD)s' | vncpasswd -f > ~/.vnc/passwd && chmod 600 ~/.vnc/passwd && ls -la ~/.vnc/passwd" +autorestart=false +startsecs=0 +priority=150 +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 + +[program:x11vnc] +command=bash -c "mkdir -p /var/log && touch /var/log/x11vnc.log && chmod 666 /var/log/x11vnc.log && sleep 5 && DISPLAY=:99 x11vnc -display :99 -forever -shared -rfbauth /root/.vnc/passwd -rfbport 5901 -o /var/log/x11vnc.log" +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +priority=200 +startretries=10 +startsecs=10 +stopsignal=TERM +stopwaitsecs=10 +depends_on=vnc_setup,xvfb + +[program:x11vnc_log] +command=bash -c "mkdir -p /var/log && touch /var/log/x11vnc.log && tail -f /var/log/x11vnc.log" +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +priority=250 +stopsignal=TERM +stopwaitsecs=5 +depends_on=x11vnc + +[program:novnc] +command=bash -c "sleep 5 && cd /opt/novnc && ./utils/novnc_proxy --vnc localhost:5901 --listen 0.0.0.0:6080 --web /opt/novnc" +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +priority=300 +startretries=5 +startsecs=3 +depends_on=x11vnc + +[program:webui] +command=python webui.py --ip 0.0.0.0 --port 7788 +directory=/app +autorestart=true +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +priority=400 +startretries=3 +startsecs=3 +stopsignal=TERM +stopwaitsecs=10 \ No newline at end of file diff --git a/tests/test_agents.py b/tests/test_agents.py new file mode 100644 index 00000000..5955b333 --- /dev/null +++ b/tests/test_agents.py @@ -0,0 +1,385 @@ +import asyncio +import os +import pdb +import sys +from pprint import pprint + +from browser_use.agent.views import AgentHistoryList +from dotenv import load_dotenv + +load_dotenv() +sys.path.append(".") + + +async def test_browser_use_agent(): + from browser_use.browser.browser import BrowserConfig + from browser_use.browser.context import BrowserContextConfig + + from src.web_ui.agent.browser_use.browser_use_agent import BrowserUseAgent + from src.web_ui.browser.custom_browser import CustomBrowser + from src.web_ui.controller.custom_controller import CustomController + from src.web_ui.utils import llm_provider + + llm = llm_provider.get_llm_model( + provider="openai", + model_name="gpt-4o", + temperature=0.8, + ) + + # llm = llm_provider.get_llm_model( + # provider="google", + # model_name="gemini-2.0-flash", + # temperature=0.6, + # api_key=os.getenv("GOOGLE_API_KEY", "") + # ) + + # llm = utils.get_llm_model( + # provider="deepseek", + # model_name="deepseek-reasoner", + # temperature=0.8 + # ) + + # llm = utils.get_llm_model( + # provider="deepseek", + # model_name="deepseek-chat", + # temperature=0.8 + # ) + + # llm = utils.get_llm_model( + # provider="ollama", model_name="qwen2.5:7b", temperature=0.5 + # ) + + # llm = utils.get_llm_model( + # provider="ollama", model_name="deepseek-r1:14b", temperature=0.5 + # ) + + window_w, window_h = 1280, 1100 + + # llm = llm_provider.get_llm_model( + # provider="azure_openai", + # model_name="gpt-4o", + # temperature=0.5, + # base_url=os.getenv("AZURE_OPENAI_ENDPOINT", ""), + # api_key=os.getenv("AZURE_OPENAI_API_KEY", ""), + # ) + + mcp_server_config = { + "mcpServers": { + # "markitdown": { + # "command": "docker", + # "args": [ + # "run", + # "--rm", + # "-i", + # "markitdown-mcp:latest" + # ] + # }, + "desktop-commander": { + "command": "npx", + "args": ["-y", "@wonderwhy-er/desktop-commander"], + }, + } + } + controller = CustomController() + await controller.setup_mcp_client(mcp_server_config) + use_own_browser = True + browser = None + browser_context = None + + try: + extra_browser_args = [] + if use_own_browser: + browser_binary_path = os.getenv("BROWSER_PATH", None) + if browser_binary_path == "": + browser_binary_path = None + browser_user_data = os.getenv("BROWSER_USER_DATA", None) + if browser_user_data: + extra_browser_args += [f"--user-data-dir={browser_user_data}"] + else: + browser_binary_path = None + browser = CustomBrowser( + config=BrowserConfig( + headless=False, + browser_binary_path=browser_binary_path, + extra_browser_args=extra_browser_args, + new_context_config=BrowserContextConfig( + window_width=window_w, + window_height=window_h, + ), + ) + ) + browser_context = await browser.new_context( + config=BrowserContextConfig( + trace_path=None, + save_recording_path=None, + save_downloads_path="./tmp/downloads", + window_height=window_h, + window_width=window_w, + ) + ) + agent = BrowserUseAgent( + # task="download pdf from https://arxiv.org/pdf/2311.16498 and rename this pdf to 'mcp-test.pdf'", + task="give me nvidia stock price", + llm=llm, + browser=browser, + browser_context=browser_context, + controller=controller, + use_vision=True, + max_actions_per_step=10, + generate_gif=True, + ) + history: AgentHistoryList = await agent.run(max_steps=100) + + print("Final Result:") + pprint(history.final_result(), indent=4) + + print("\nErrors:") + pprint(history.errors(), indent=4) + + except Exception: + import traceback + + traceback.print_exc() + finally: + if browser_context: + await browser_context.close() + if browser: + await browser.close() + if controller: + await controller.close_mcp_client() + + +async def test_browser_use_parallel(): + from browser_use.browser.browser import BrowserConfig + from browser_use.browser.context import ( + BrowserContextConfig, + ) + + from src.web_ui.agent.browser_use.browser_use_agent import BrowserUseAgent + from src.web_ui.browser.custom_browser import CustomBrowser + from src.web_ui.controller.custom_controller import CustomController + from src.web_ui.utils import llm_provider + + # llm = utils.get_llm_model( + # provider="openai", + # model_name="gpt-4o", + # temperature=0.8, + # base_url=os.getenv("OPENAI_ENDPOINT", ""), + # api_key=os.getenv("OPENAI_API_KEY", ""), + # ) + + # llm = utils.get_llm_model( + # provider="google", + # model_name="gemini-2.0-flash", + # temperature=0.6, + # api_key=os.getenv("GOOGLE_API_KEY", "") + # ) + + # llm = utils.get_llm_model( + # provider="deepseek", + # model_name="deepseek-reasoner", + # temperature=0.8 + # ) + + # llm = utils.get_llm_model( + # provider="deepseek", + # model_name="deepseek-chat", + # temperature=0.8 + # ) + + # llm = utils.get_llm_model( + # provider="ollama", model_name="qwen2.5:7b", temperature=0.5 + # ) + + # llm = utils.get_llm_model( + # provider="ollama", model_name="deepseek-r1:14b", temperature=0.5 + # ) + + window_w, window_h = 1280, 1100 + + llm = llm_provider.get_llm_model( + provider="azure_openai", + model_name="gpt-4o", + temperature=0.5, + base_url=os.getenv("AZURE_OPENAI_ENDPOINT", ""), + api_key=os.getenv("AZURE_OPENAI_API_KEY", ""), + ) + + mcp_server_config = { + "mcpServers": { + # "markitdown": { + # "command": "docker", + # "args": [ + # "run", + # "--rm", + # "-i", + # "markitdown-mcp:latest" + # ] + # }, + "desktop-commander": { + "command": "npx", + "args": ["-y", "@wonderwhy-er/desktop-commander"], + }, + # "filesystem": { + # "command": "npx", + # "args": [ + # "-y", + # "@modelcontextprotocol/server-filesystem", + # "/Users/xxx/ai_workspace", + # ] + # }, + } + } + controller = CustomController() + await controller.setup_mcp_client(mcp_server_config) + use_own_browser = True + browser = None + browser_context = None + + try: + extra_browser_args = [] + if use_own_browser: + browser_binary_path = os.getenv("BROWSER_PATH", None) + if browser_binary_path == "": + browser_binary_path = None + browser_user_data = os.getenv("BROWSER_USER_DATA", None) + if browser_user_data: + extra_browser_args += [f"--user-data-dir={browser_user_data}"] + else: + browser_binary_path = None + browser = CustomBrowser( + config=BrowserConfig( + headless=False, + browser_binary_path=browser_binary_path, + extra_browser_args=extra_browser_args, + new_context_config=BrowserContextConfig( + window_width=window_w, + window_height=window_h, + ), + ) + ) + browser_context = await browser.new_context( + config=BrowserContextConfig( + trace_path=None, + save_recording_path=None, + save_downloads_path="./tmp/downloads", + window_height=window_h, + window_width=window_w, + force_new_context=True, + ) + ) + agents = [ + BrowserUseAgent(task=task, llm=llm, browser=browser, controller=controller) + for task in [ + "Search Google for weather in Tokyo", + # 'Check Reddit front page title', + # 'Find NASA image of the day', + # 'Check top story on CNN', + # 'Search latest SpaceX launch date', + # 'Look up population of Paris', + "Find current time in Sydney", + "Check who won last Super Bowl", + # 'Search trending topics on Twitter', + ] + ] + + histories = await asyncio.gather(*[agent.run() for agent in agents]) + print("Final Results:") + for i, history in enumerate(histories): + print(f"Agent {i + 1}:") + pprint(history.final_result(), indent=4) + print(f"Errors: {history.errors()}") + print() + + pdb.set_trace() + + except Exception: + import traceback + + traceback.print_exc() + finally: + if browser_context: + await browser_context.close() + if browser: + await browser.close() + if controller: + await controller.close_mcp_client() + + +async def test_deep_research_agent(): + from src.web_ui.agent.deep_research.deep_research_agent import ( + DeepResearchAgent, + ) + from src.web_ui.utils import llm_provider + + llm = llm_provider.get_llm_model(provider="openai", model_name="gpt-4o", temperature=0.5) + + # llm = llm_provider.get_llm_model( + # provider="bedrock", + # ) + + mcp_server_config = { + "mcpServers": { + "desktop-commander": { + "command": "npx", + "args": ["-y", "@wonderwhy-er/desktop-commander"], + }, + } + } + + browser_config = { + "headless": False, + "window_width": 1280, + "window_height": 1100, + "use_own_browser": False, + } + agent = DeepResearchAgent( + llm=llm, browser_config=browser_config, mcp_server_config=mcp_server_config + ) + research_topic = "Give me investment advices of nvidia and tesla." + task_id_to_resume = "" # Set this to resume a previous task ID + + print(f"Starting research on: {research_topic}") + + try: + # Call run and wait for the final result dictionary + result = await agent.run( + research_topic, + task_id=task_id_to_resume, + save_dir="./tmp/deep_research", + max_parallel_browsers=1, + ) + + print("\n--- Research Process Ended ---") + print(f"Status: {result.get('status')}") + print(f"Message: {result.get('message')}") + print(f"Task ID: {result.get('task_id')}") + + # Check the final state for the report + final_state = result.get("final_state", {}) + if final_state: + print("\n--- Final State Summary ---") + print( + f" Plan Steps Completed: {sum(1 for item in final_state.get('research_plan', []) if item.get('status') == 'completed')}" + ) + print(f" Total Search Results Logged: {len(final_state.get('search_results', []))}") + if final_state.get("final_report"): + print( + " Final Report: Generated (content omitted). You can find it in the output directory." + ) + # print("\n--- Final Report ---") # Optionally print report + # print(final_state["final_report"]) + else: + print(" Final Report: Not generated.") + else: + print("Final state information not available.") + + except Exception as e: + print("\n--- An unhandled error occurred outside the agent run ---") + print(e) + + +if __name__ == "__main__": + asyncio.run(test_browser_use_agent()) + # asyncio.run(test_browser_use_parallel()) + # asyncio.run(test_deep_research_agent()) diff --git a/tests/test_browser_use.py b/tests/test_browser_use.py deleted file mode 100644 index 84ed23a9..00000000 --- a/tests/test_browser_use.py +++ /dev/null @@ -1,207 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @ProjectName: browser-use-webui -# @FileName: test_browser_use.py -import pdb - -from dotenv import load_dotenv - -load_dotenv() -import sys - -sys.path.append(".") -import os -import sys -from pprint import pprint - -import asyncio -from browser_use import Agent -from browser_use.agent.views import AgentHistoryList - -from src.utils import utils - - -async def test_browser_use_org(): - from browser_use.browser.browser import Browser, BrowserConfig - from browser_use.browser.context import ( - BrowserContext, - BrowserContextConfig, - BrowserContextWindowSize, - ) - llm = utils.get_llm_model( - provider="azure_openai", - model_name="gpt-4o", - temperature=0.8, - base_url=os.getenv("AZURE_OPENAI_ENDPOINT", ""), - api_key=os.getenv("AZURE_OPENAI_API_KEY", "") - ) - - window_w, window_h = 1920, 1080 - - browser = Browser( - config=BrowserConfig( - headless=False, - disable_security=True, - extra_chromium_args=[f'--window-size={window_w},{window_h}'], - ) - ) - async with await browser.new_context( - config=BrowserContextConfig( - trace_path='./tmp/traces', - save_recording_path="./tmp/record_videos", - no_viewport=False, - browser_window_size=BrowserContextWindowSize(width=window_w, height=window_h), - ) - ) as browser_context: - agent = Agent( - task="go to google.com and type 'OpenAI' click search and give me the first url", - llm=llm, - browser_context=browser_context, - ) - history: AgentHistoryList = await agent.run(max_steps=10) - - print('Final Result:') - pprint(history.final_result(), indent=4) - - print('\nErrors:') - pprint(history.errors(), indent=4) - - # e.g. xPaths the model clicked on - print('\nModel Outputs:') - pprint(history.model_actions(), indent=4) - - print('\nThoughts:') - pprint(history.model_thoughts(), indent=4) - # close browser - await browser.close() - - -async def test_browser_use_custom(): - from playwright.async_api import async_playwright - from browser_use.browser.context import BrowserContextWindowSize - - from src.browser.custom_browser import CustomBrowser, BrowserConfig - from src.browser.custom_context import BrowserContext, BrowserContextConfig - from src.controller.custom_controller import CustomController - from src.agent.custom_agent import CustomAgent - from src.agent.custom_prompts import CustomSystemPrompt - from src.browser.custom_context import CustomBrowserContext - - window_w, window_h = 1920, 1080 - - # llm = utils.get_llm_model( - # provider="azure_openai", - # model_name="gpt-4o", - # temperature=0.8, - # base_url=os.getenv("AZURE_OPENAI_ENDPOINT", ""), - # api_key=os.getenv("AZURE_OPENAI_API_KEY", "") - # ) - - # llm = utils.get_llm_model( - # provider="gemini", - # model_name="gemini-2.0-flash-exp", - # temperature=1.0, - # api_key=os.getenv("GOOGLE_API_KEY", "") - # ) - - # llm = utils.get_llm_model( - # provider="deepseek", - # model_name="deepseek-chat", - # temperature=0.8 - # ) - - llm = utils.get_llm_model( - provider="ollama", - model_name="qwen2.5:7b", - temperature=0.8 - ) - - controller = CustomController() - use_own_browser = False - disable_security = True - use_vision = False - playwright = None - browser_context_ = None - try: - if use_own_browser: - playwright = await async_playwright().start() - chrome_exe = os.getenv("CHROME_PATH", "") - chrome_use_data = os.getenv("CHROME_USER_DATA", "") - browser_context_ = await playwright.chromium.launch_persistent_context( - user_data_dir=chrome_use_data, - executable_path=chrome_exe, - no_viewport=False, - headless=False, # 保持浏览器窗口可见 - user_agent=( - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' - '(KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36' - ), - java_script_enabled=True, - bypass_csp=disable_security, - ignore_https_errors=disable_security, - record_video_dir="./tmp/record_videos", - record_video_size={'width': window_w, 'height': window_h} - ) - else: - browser_context_ = None - - browser = CustomBrowser( - config=BrowserConfig( - headless=False, - disable_security=True, - extra_chromium_args=[f'--window-size={window_w},{window_h}'], - ) - ) - - async with await browser.new_context( - config=BrowserContextConfig( - trace_path='./tmp/result_processing', - save_recording_path="./tmp/record_videos", - no_viewport=False, - browser_window_size=BrowserContextWindowSize(width=window_w, height=window_h), - ), - context=browser_context_ - ) as browser_context: - agent = CustomAgent( - task="go to google.com and type 'OpenAI' click search and give me the first url", - add_infos="", # some hints for llm to complete the task - llm=llm, - browser_context=browser_context, - controller=controller, - system_prompt_class=CustomSystemPrompt, - use_vision=use_vision - ) - history: AgentHistoryList = await agent.run(max_steps=10) - - print('Final Result:') - pprint(history.final_result(), indent=4) - - print('\nErrors:') - pprint(history.errors(), indent=4) - - # e.g. xPaths the model clicked on - print('\nModel Outputs:') - pprint(history.model_actions(), indent=4) - - print('\nThoughts:') - pprint(history.model_thoughts(), indent=4) - # close browser - except Exception as e: - import traceback - traceback.print_exc() - finally: - # 显式关闭持久化上下文 - if browser_context_: - await browser_context_.close() - - # 关闭 Playwright 对象 - if playwright: - await playwright.stop() - - await browser.close() - - -if __name__ == '__main__': - # asyncio.run(test_browser_use_org()) - asyncio.run(test_browser_use_custom()) diff --git a/tests/test_controller.py b/tests/test_controller.py new file mode 100644 index 00000000..e0520b6a --- /dev/null +++ b/tests/test_controller.py @@ -0,0 +1,148 @@ +import asyncio +import pdb +import sys +import time + +sys.path.append(".") + +from dotenv import load_dotenv + +load_dotenv() + + +async def test_mcp_client(): + from src.web_ui.utils.mcp_client import create_tool_param_model, setup_mcp_client_and_tools + + test_server_config = { + "mcpServers": { + # "markitdown": { + # "command": "docker", + # "args": [ + # "run", + # "--rm", + # "-i", + # "markitdown-mcp:latest" + # ] + # }, + "desktop-commander": { + "command": "npx", + "args": ["-y", "@wonderwhy-er/desktop-commander"], + }, + # "filesystem": { + # "command": "npx", + # "args": [ + # "-y", + # "@modelcontextprotocol/server-filesystem", + # "/Users/xxx/ai_workspace", + # ] + # }, + } + } + + mcp_client = await setup_mcp_client_and_tools(test_server_config) + + if not mcp_client: + print("Failed to setup MCP client") + return + + # Get tools from the client + mcp_tools = [] + if hasattr(mcp_client, "clients") and hasattr(mcp_client.clients, "items"): + for _server_name, server_client in mcp_client.clients.items(): # type: ignore[attr-defined] + tools = await server_client.list_tools() # type: ignore[attr-defined] + mcp_tools.extend(tools) + else: + # Alternative approach if clients attribute doesn't exist + try: + tools = await mcp_client.list_tools() # type: ignore[attr-defined] + mcp_tools.extend(tools) + except Exception as e: + print(f"Failed to get tools: {e}") + return + + for tool in mcp_tools: + tool_param_model = create_tool_param_model(tool) + print(tool.name) + print(tool.description) + try: + print(tool_param_model.model_json_schema()) + except AttributeError: + # Fallback for older Pydantic versions + print(tool_param_model().schema()) # type: ignore[deprecated] + pdb.set_trace() + + +async def test_controller_with_mcp(): + from src.web_ui.controller.custom_controller import CustomController + + mcp_server_config = { + "mcpServers": { + # "markitdown": { + # "command": "docker", + # "args": [ + # "run", + # "--rm", + # "-i", + # "markitdown-mcp:latest" + # ] + # }, + "desktop-commander": { + "command": "npx", + "args": ["-y", "@wonderwhy-er/desktop-commander"], + }, + # "filesystem": { + # "command": "npx", + # "args": [ + # "-y", + # "@modelcontextprotocol/server-filesystem", + # "/Users/xxx/ai_workspace", + # ] + # }, + } + } + + controller = CustomController() + await controller.setup_mcp_client(mcp_server_config) + action_name = "mcp.desktop-commander.execute_command" + action_info = controller.registry.registry.actions[action_name] + param_model = action_info.param_model + print(param_model.model_json_schema()) + params = {"command": "python ./tmp/test.py"} + validated_params = param_model(**params) + ActionModel_ = controller.registry.create_action_model() + # Create ActionModel instance with the validated parameters + action_model = ActionModel_(**{action_name: validated_params}) + result = await controller.act(action_model) + result = result.extracted_content + print(result) + if ( + result + and "Command is still running. Use read_output to get more output." in result + and "PID" in result.split("\n")[0] + ): + pid = int(result.split("\n")[0].split("PID")[-1].strip()) + action_name = "mcp.desktop-commander.read_output" + action_info = controller.registry.registry.actions[action_name] + param_model = action_info.param_model + print(param_model.model_json_schema()) + params = {"pid": pid} + validated_params = param_model(**params) + action_model = ActionModel_(**{action_name: validated_params}) + output_result = "" + while True: + time.sleep(1) + result = await controller.act(action_model) + result = result.extracted_content + if result: + pdb.set_trace() + output_result = result + break + print(output_result) + pdb.set_trace() + await controller.close_mcp_client() + pdb.set_trace() + + +if __name__ == "__main__": + # asyncio.run(test_mcp_client()) + asyncio.run(test_controller_with_mcp()) diff --git a/tests/test_llm_api.py b/tests/test_llm_api.py index 9e2a1d6d..fc2bf96a 100644 --- a/tests/test_llm_api.py +++ b/tests/test_llm_api.py @@ -1,131 +1,167 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: test_llm_api.py import os import pdb +import sys +from dataclasses import dataclass from dotenv import load_dotenv +from langchain_core.messages import HumanMessage, SystemMessage load_dotenv() +sys.path.append(".") -import sys -sys.path.append(".") +@dataclass +class LLMConfig: + provider: str + model_name: str + temperature: float = 0.8 + base_url: str | None = None + api_key: str | None = None -def test_openai_model(): - from langchain_core.messages import HumanMessage - from src.utils import utils - - llm = utils.get_llm_model( - provider="openai", - model_name="gpt-4o", - temperature=0.8, - base_url=os.getenv("OPENAI_ENDPOINT", ""), - api_key=os.getenv("OPENAI_API_KEY", "") - ) - image_path = "assets/examples/test.png" - image_data = utils.encode_image(image_path) - message = HumanMessage( - content=[ - {"type": "text", "text": "describe this image"}, +def create_message_content(text, image_path=None): + content = [{"type": "text", "text": text}] + image_format = "png" if image_path and image_path.endswith(".png") else "jpeg" + if image_path: + from src.web_ui.utils import utils + + image_data = utils.encode_image(image_path) + content.append( { "type": "image_url", - "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}, - }, - ] + "image_url": {"url": f"data:image/{image_format};base64,{image_data}"}, + } + ) + return content + + +def get_env_value(key, provider): + env_mappings = { + "openai": {"api_key": "OPENAI_API_KEY", "base_url": "OPENAI_ENDPOINT"}, + "azure_openai": {"api_key": "AZURE_OPENAI_API_KEY", "base_url": "AZURE_OPENAI_ENDPOINT"}, + "google": {"api_key": "GOOGLE_API_KEY"}, + "deepseek": {"api_key": "DEEPSEEK_API_KEY", "base_url": "DEEPSEEK_ENDPOINT"}, + "mistral": {"api_key": "MISTRAL_API_KEY", "base_url": "MISTRAL_ENDPOINT"}, + "alibaba": {"api_key": "ALIBABA_API_KEY", "base_url": "ALIBABA_ENDPOINT"}, + "moonshot": {"api_key": "MOONSHOT_API_KEY", "base_url": "MOONSHOT_ENDPOINT"}, + "ibm": {"api_key": "IBM_API_KEY", "base_url": "IBM_ENDPOINT"}, + } + + if provider in env_mappings and key in env_mappings[provider]: + return os.getenv(env_mappings[provider][key], "") + return "" + + +def test_llm(config, query, image_path=None, system_message=None): + from src.web_ui.utils import llm_provider + + # Special handling for Ollama-based models + if config.provider == "ollama": + if "deepseek-r1" in config.model_name: + from src.web_ui.utils.llm_provider import DeepSeekR1ChatOllama + + llm = DeepSeekR1ChatOllama(model=config.model_name) + else: + from langchain_ollama import ChatOllama + + llm = ChatOllama(model=config.model_name) + + ai_msg = llm.invoke(query) + print(ai_msg.content) + if "deepseek-r1" in config.model_name: + pdb.set_trace() + return + + # For other providers, use the standard configuration + llm = llm_provider.get_llm_model( + provider=config.provider, + model_name=config.model_name, + temperature=config.temperature, + base_url=config.base_url or get_env_value("base_url", config.provider), + api_key=config.api_key or get_env_value("api_key", config.provider), ) - ai_msg = llm.invoke([message]) + + # Prepare messages for non-Ollama models + messages = [] + if system_message: + messages.append(SystemMessage(content=create_message_content(system_message))) + messages.append(HumanMessage(content=create_message_content(query, image_path))) + ai_msg = llm.invoke(messages) + + # Handle different response types + if hasattr(ai_msg, "reasoning_content"): + print(ai_msg.reasoning_content) print(ai_msg.content) -def test_gemini_model(): - # you need to enable your api key first: https://ai.google.dev/palm_docs/oauth_quickstart - from langchain_core.messages import HumanMessage - from src.utils import utils +def test_openai_model(): + config = LLMConfig(provider="openai", model_name="gpt-4o") + test_llm(config, "Describe this image", "assets/examples/test.png") - llm = utils.get_llm_model( - provider="gemini", - model_name="gemini-2.0-flash-exp", - temperature=0.8, - api_key=os.getenv("GOOGLE_API_KEY", "") - ) - image_path = "assets/examples/test.png" - image_data = utils.encode_image(image_path) - message = HumanMessage( - content=[ - {"type": "text", "text": "describe this image"}, - { - "type": "image_url", - "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}, - }, - ] - ) - ai_msg = llm.invoke([message]) - print(ai_msg.content) +def test_google_model(): + # Enable your API key first if you haven't: https://ai.google.dev/palm_docs/oauth_quickstart + config = LLMConfig(provider="google", model_name="gemini-2.0-flash-exp") + test_llm(config, "Describe this image", "assets/examples/test.png") def test_azure_openai_model(): - from langchain_core.messages import HumanMessage - from src.utils import utils - - llm = utils.get_llm_model( - provider="azure_openai", - model_name="gpt-4o", - temperature=0.8, - base_url=os.getenv("AZURE_OPENAI_ENDPOINT", ""), - api_key=os.getenv("AZURE_OPENAI_API_KEY", "") - ) - image_path = "assets/examples/test.png" - image_data = utils.encode_image(image_path) - message = HumanMessage( - content=[ - {"type": "text", "text": "describe this image"}, - { - "type": "image_url", - "image_url": {"url": f"data:image/jpeg;base64,{image_data}"}, - }, - ] - ) - ai_msg = llm.invoke([message]) - print(ai_msg.content) + config = LLMConfig(provider="azure_openai", model_name="gpt-4o") + test_llm(config, "Describe this image", "assets/examples/test.png") def test_deepseek_model(): - from langchain_core.messages import HumanMessage - from src.utils import utils - - llm = utils.get_llm_model( - provider="deepseek", - model_name="deepseek-chat", - temperature=0.8, - base_url=os.getenv("DEEPSEEK_ENDPOINT", ""), - api_key=os.getenv("DEEPSEEK_API_KEY", "") - ) - message = HumanMessage( - content=[ - {"type": "text", "text": "who are you?"} - ] + config = LLMConfig(provider="deepseek", model_name="deepseek-chat") + test_llm(config, "Who are you?") + + +def test_deepseek_r1_model(): + config = LLMConfig(provider="deepseek", model_name="deepseek-reasoner") + test_llm( + config, "Which is greater, 9.11 or 9.8?", system_message="You are a helpful AI assistant." ) - ai_msg = llm.invoke([message]) - print(ai_msg.content) def test_ollama_model(): - from langchain_ollama import ChatOllama + config = LLMConfig(provider="ollama", model_name="qwen2.5:7b") + test_llm(config, "Sing a ballad of LangChain.") - llm = ChatOllama(model="qwen2.5:7b") - ai_msg = llm.invoke("Sing a ballad of LangChain.") - print(ai_msg.content) + +def test_deepseek_r1_ollama_model(): + config = LLMConfig(provider="ollama", model_name="deepseek-r1:14b") + test_llm(config, "How many 'r's are in the word 'strawberry'?") + + +def test_mistral_model(): + config = LLMConfig(provider="mistral", model_name="pixtral-large-latest") + test_llm(config, "Describe this image", "assets/examples/test.png") + + +def test_moonshot_model(): + config = LLMConfig(provider="moonshot", model_name="moonshot-v1-32k-vision-preview") + test_llm(config, "Describe this image", "assets/examples/test.png") + + +def test_ibm_model(): + config = LLMConfig( + provider="ibm", model_name="meta-llama/llama-4-maverick-17b-128e-instruct-fp8" + ) + test_llm(config, "Describe this image", "assets/examples/test.png") + + +def test_qwen_model(): + config = LLMConfig(provider="alibaba", model_name="qwen-vl-max") + test_llm(config, "How many 'r's are in the word 'strawberry'?") -if __name__ == '__main__': +if __name__ == "__main__": # test_openai_model() - # test_gemini_model() - # test_azure_openai_model() + # test_google_model() + test_azure_openai_model() # test_deepseek_model() - test_ollama_model() + # test_ollama_model() + # test_deepseek_r1_model() + # test_deepseek_r1_ollama_model() + # test_mistral_model() + # test_ibm_model() + # test_qwen_model() diff --git a/tests/test_playwright.py b/tests/test_playwright.py index cc28a28d..dd043cc7 100644 --- a/tests/test_playwright.py +++ b/tests/test_playwright.py @@ -1,10 +1,3 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/2 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: test_playwright.py -import pdb from dotenv import load_dotenv load_dotenv() @@ -12,6 +5,7 @@ def test_connect_browser(): import os + from playwright.sync_api import sync_playwright chrome_exe = os.getenv("CHROME_PATH", "") @@ -21,17 +15,17 @@ def test_connect_browser(): browser = p.chromium.launch_persistent_context( user_data_dir=chrome_use_data, executable_path=chrome_exe, - headless=False # 保持浏览器窗口可见 + headless=False, # Keep browser window visible ) page = browser.new_page() page.goto("https://mail.google.com/mail/u/0/#inbox") page.wait_for_load_state() - input("按下回车键以关闭浏览器...") + input("Press the Enter key to close the browser...") browser.close() -if __name__ == '__main__': +if __name__ == "__main__": test_connect_browser() diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..7c581874 --- /dev/null +++ b/uv.lock @@ -0,0 +1,6932 @@ +version = 1 +revision = 3 +requires-python = ">=3.11, <3.15" +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", + "python_full_version >= '3.12.4' and python_full_version < '3.13'", + "python_full_version >= '3.12' and python_full_version < '3.12.4'", + "python_full_version < '3.12'", +] + +[[package]] +name = "aiofiles" +version = "24.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/03/a88171e277e8caa88a4c77808c20ebb04ba74cc4681bf1e9416c862de237/aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", size = 30247, upload-time = "2024-06-24T11:02:03.584Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/45/30bb92d442636f570cb5651bc661f52b610e2eec3f891a5dc3a4c3667db0/aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5", size = 15896, upload-time = "2024-06-24T11:02:01.529Z" }, +] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/30/f84a107a9c4331c14b2b586036f40965c128aa4fee4dda5d3d51cb14ad54/aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558", size = 22760, upload-time = "2025-03-12T01:42:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/15/5bf3b99495fb160b63f95972b81750f18f7f4e02ad051373b669d17d44f2/aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8", size = 15265, upload-time = "2025-03-12T01:42:47.083Z" }, +] + +[[package]] +name = "aiohttp" +version = "3.12.15" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohappyeyeballs" }, + { name = "aiosignal" }, + { name = "attrs" }, + { name = "frozenlist" }, + { name = "multidict" }, + { name = "propcache" }, + { name = "yarl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, + { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, + { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, + { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, + { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, + { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, + { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, + { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, + { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, + { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, + { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, + { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, +] + +[[package]] +name = "aiosignal" +version = "1.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "frozenlist" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/62/06741b579156360248d1ec624842ad0edf697050bbaf7c3e46394e106ad1/aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7", size = 25007, upload-time = "2025-07-03T22:54:43.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490, upload-time = "2025-07-03T22:54:42.156Z" }, +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anthropic" +version = "0.71.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/4f/70682b068d897841f43223df82d96ec1d617435a8b759c4a2d901a50158b/anthropic-0.71.0.tar.gz", hash = "sha256:eb8e6fa86d049061b3ef26eb4cbae0174ebbff21affa6de7b3098da857d8de6a", size = 489102, upload-time = "2025-10-16T15:54:40.08Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/77/073e8ac488f335aec7001952825275582fb8f433737e90f24eeef9d878f6/anthropic-0.71.0-py3-none-any.whl", hash = "sha256:85c5015fcdbdc728390f11b17642a65a4365d03b12b799b18b6cc57e71fdb327", size = 355035, upload-time = "2025-10-16T15:54:38.238Z" }, +] + +[[package]] +name = "anyio" +version = "4.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/78/7d432127c41b50bccba979505f272c16cbcadcc33645d5fa3a738110ae75/anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4", size = 219094, upload-time = "2025-09-23T09:19:12.58Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "audioop-lts" +version = "0.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/53/946db57842a50b2da2e0c1e34bd37f36f5aadba1a929a3971c5d7841dbca/audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0", size = 30686, upload-time = "2025-08-05T16:43:17.409Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/d4/94d277ca941de5a507b07f0b592f199c22454eeaec8f008a286b3fbbacd6/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800", size = 46523, upload-time = "2025-08-05T16:42:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5a/656d1c2da4b555920ce4177167bfeb8623d98765594af59702c8873f60ec/audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303", size = 27455, upload-time = "2025-08-05T16:42:22.283Z" }, + { url = "https://files.pythonhosted.org/packages/1b/83/ea581e364ce7b0d41456fb79d6ee0ad482beda61faf0cab20cbd4c63a541/audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75", size = 26997, upload-time = "2025-08-05T16:42:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/b8/3b/e8964210b5e216e5041593b7d33e97ee65967f17c282e8510d19c666dab4/audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d", size = 85844, upload-time = "2025-08-05T16:42:25.208Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2e/0a1c52faf10d51def20531a59ce4c706cb7952323b11709e10de324d6493/audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b", size = 85056, upload-time = "2025-08-05T16:42:26.559Z" }, + { url = "https://files.pythonhosted.org/packages/75/e8/cd95eef479656cb75ab05dfece8c1f8c395d17a7c651d88f8e6e291a63ab/audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8", size = 93892, upload-time = "2025-08-05T16:42:27.902Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1e/a0c42570b74f83efa5cca34905b3eef03f7ab09fe5637015df538a7f3345/audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc", size = 96660, upload-time = "2025-08-05T16:42:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/8a0ae607ca07dbb34027bac8db805498ee7bfecc05fd2c148cc1ed7646e7/audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3", size = 79143, upload-time = "2025-08-05T16:42:29.929Z" }, + { url = "https://files.pythonhosted.org/packages/12/17/0d28c46179e7910bfb0bb62760ccb33edb5de973052cb2230b662c14ca2e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6", size = 84313, upload-time = "2025-08-05T16:42:30.949Z" }, + { url = "https://files.pythonhosted.org/packages/84/ba/bd5d3806641564f2024e97ca98ea8f8811d4e01d9b9f9831474bc9e14f9e/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a", size = 93044, upload-time = "2025-08-05T16:42:31.959Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/435ce8d5642f1f7679540d1e73c1c42d933331c0976eb397d1717d7f01a3/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623", size = 78766, upload-time = "2025-08-05T16:42:33.302Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3b/b909e76b606cbfd53875693ec8c156e93e15a1366a012f0b7e4fb52d3c34/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7", size = 87640, upload-time = "2025-08-05T16:42:34.854Z" }, + { url = "https://files.pythonhosted.org/packages/30/e7/8f1603b4572d79b775f2140d7952f200f5e6c62904585d08a01f0a70393a/audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449", size = 86052, upload-time = "2025-08-05T16:42:35.839Z" }, + { url = "https://files.pythonhosted.org/packages/b5/96/c37846df657ccdda62ba1ae2b6534fa90e2e1b1742ca8dcf8ebd38c53801/audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636", size = 26185, upload-time = "2025-08-05T16:42:37.04Z" }, + { url = "https://files.pythonhosted.org/packages/34/a5/9d78fdb5b844a83da8a71226c7bdae7cc638861085fff7a1d707cb4823fa/audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e", size = 30503, upload-time = "2025-08-05T16:42:38.427Z" }, + { url = "https://files.pythonhosted.org/packages/34/25/20d8fde083123e90c61b51afb547bb0ea7e77bab50d98c0ab243d02a0e43/audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f", size = 24173, upload-time = "2025-08-05T16:42:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/58/a7/0a764f77b5c4ac58dc13c01a580f5d32ae8c74c92020b961556a43e26d02/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09", size = 47096, upload-time = "2025-08-05T16:42:40.684Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/ebebedde1a18848b085ad0fa54b66ceb95f1f94a3fc04f1cd1b5ccb0ed42/audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58", size = 27748, upload-time = "2025-08-05T16:42:41.992Z" }, + { url = "https://files.pythonhosted.org/packages/cb/6e/11ca8c21af79f15dbb1c7f8017952ee8c810c438ce4e2b25638dfef2b02c/audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19", size = 27329, upload-time = "2025-08-05T16:42:42.987Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/0022f93d56d85eec5da6b9da6a958a1ef09e80c39f2cc0a590c6af81dcbb/audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911", size = 92407, upload-time = "2025-08-05T16:42:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/87/1d/48a889855e67be8718adbc7a01f3c01d5743c325453a5e81cf3717664aad/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9", size = 91811, upload-time = "2025-08-05T16:42:45.325Z" }, + { url = "https://files.pythonhosted.org/packages/98/a6/94b7213190e8077547ffae75e13ed05edc488653c85aa5c41472c297d295/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe", size = 100470, upload-time = "2025-08-05T16:42:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/e9/e9/78450d7cb921ede0cfc33426d3a8023a3bda755883c95c868ee36db8d48d/audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132", size = 103878, upload-time = "2025-08-05T16:42:47.576Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e2/cd5439aad4f3e34ae1ee852025dc6aa8f67a82b97641e390bf7bd9891d3e/audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753", size = 84867, upload-time = "2025-08-05T16:42:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/68/4b/9d853e9076c43ebba0d411e8d2aa19061083349ac695a7d082540bad64d0/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb", size = 90001, upload-time = "2025-08-05T16:42:50.038Z" }, + { url = "https://files.pythonhosted.org/packages/58/26/4bae7f9d2f116ed5593989d0e521d679b0d583973d203384679323d8fa85/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093", size = 99046, upload-time = "2025-08-05T16:42:51.111Z" }, + { url = "https://files.pythonhosted.org/packages/b2/67/a9f4fb3e250dda9e9046f8866e9fa7d52664f8985e445c6b4ad6dfb55641/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7", size = 84788, upload-time = "2025-08-05T16:42:52.198Z" }, + { url = "https://files.pythonhosted.org/packages/70/f7/3de86562db0121956148bcb0fe5b506615e3bcf6e63c4357a612b910765a/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c", size = 94472, upload-time = "2025-08-05T16:42:53.59Z" }, + { url = "https://files.pythonhosted.org/packages/f1/32/fd772bf9078ae1001207d2df1eef3da05bea611a87dd0e8217989b2848fa/audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5", size = 92279, upload-time = "2025-08-05T16:42:54.632Z" }, + { url = "https://files.pythonhosted.org/packages/4f/41/affea7181592ab0ab560044632571a38edaf9130b84928177823fbf3176a/audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917", size = 26568, upload-time = "2025-08-05T16:42:55.627Z" }, + { url = "https://files.pythonhosted.org/packages/28/2b/0372842877016641db8fc54d5c88596b542eec2f8f6c20a36fb6612bf9ee/audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547", size = 30942, upload-time = "2025-08-05T16:42:56.674Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/baf2b9cc7e96c179bb4a54f30fcd83e6ecb340031bde68f486403f943768/audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969", size = 24603, upload-time = "2025-08-05T16:42:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/5c/73/413b5a2804091e2c7d5def1d618e4837f1cb82464e230f827226278556b7/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6", size = 47104, upload-time = "2025-08-05T16:42:58.518Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8c/daa3308dc6593944410c2c68306a5e217f5c05b70a12e70228e7dd42dc5c/audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a", size = 27754, upload-time = "2025-08-05T16:43:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/4e/86/c2e0f627168fcf61781a8f72cab06b228fe1da4b9fa4ab39cfb791b5836b/audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b", size = 27332, upload-time = "2025-08-05T16:43:01.666Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bd/35dce665255434f54e5307de39e31912a6f902d4572da7c37582809de14f/audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6", size = 92396, upload-time = "2025-08-05T16:43:02.991Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d2/deeb9f51def1437b3afa35aeb729d577c04bcd89394cb56f9239a9f50b6f/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf", size = 91811, upload-time = "2025-08-05T16:43:04.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/3b/09f8b35b227cee28cc8231e296a82759ed80c1a08e349811d69773c48426/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd", size = 100483, upload-time = "2025-08-05T16:43:05.085Z" }, + { url = "https://files.pythonhosted.org/packages/0b/15/05b48a935cf3b130c248bfdbdea71ce6437f5394ee8533e0edd7cfd93d5e/audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a", size = 103885, upload-time = "2025-08-05T16:43:06.197Z" }, + { url = "https://files.pythonhosted.org/packages/83/80/186b7fce6d35b68d3d739f228dc31d60b3412105854edb975aa155a58339/audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e", size = 84899, upload-time = "2025-08-05T16:43:07.291Z" }, + { url = "https://files.pythonhosted.org/packages/49/89/c78cc5ac6cb5828f17514fb12966e299c850bc885e80f8ad94e38d450886/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7", size = 89998, upload-time = "2025-08-05T16:43:08.335Z" }, + { url = "https://files.pythonhosted.org/packages/4c/4b/6401888d0c010e586c2ca50fce4c903d70a6bb55928b16cfbdfd957a13da/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5", size = 99046, upload-time = "2025-08-05T16:43:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/de/f8/c874ca9bb447dae0e2ef2e231f6c4c2b0c39e31ae684d2420b0f9e97ee68/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9", size = 84843, upload-time = "2025-08-05T16:43:10.749Z" }, + { url = "https://files.pythonhosted.org/packages/3e/c0/0323e66f3daebc13fd46b36b30c3be47e3fc4257eae44f1e77eb828c703f/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602", size = 94490, upload-time = "2025-08-05T16:43:12.131Z" }, + { url = "https://files.pythonhosted.org/packages/98/6b/acc7734ac02d95ab791c10c3f17ffa3584ccb9ac5c18fd771c638ed6d1f5/audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0", size = 92297, upload-time = "2025-08-05T16:43:13.139Z" }, + { url = "https://files.pythonhosted.org/packages/13/c3/c3dc3f564ce6877ecd2a05f8d751b9b27a8c320c2533a98b0c86349778d0/audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3", size = 27331, upload-time = "2025-08-05T16:43:14.19Z" }, + { url = "https://files.pythonhosted.org/packages/72/bb/b4608537e9ffcb86449091939d52d24a055216a36a8bf66b936af8c3e7ac/audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b", size = 31697, upload-time = "2025-08-05T16:43:15.193Z" }, + { url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, +] + +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + +[[package]] +name = "beautifulsoup4" +version = "4.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "soupsieve" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/e9/df2358efd7659577435e2177bfa69cba6c33216681af51a707193dec162a/beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e", size = 625822, upload-time = "2025-09-29T10:05:42.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/fe/3aed5d0be4d404d12d36ab97e2f1791424d9ca39c2f754a6285d59a3b01d/beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515", size = 106392, upload-time = "2025-09-29T10:05:43.771Z" }, +] + +[[package]] +name = "boto3" +version = "1.40.56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, + { name = "jmespath" }, + { name = "s3transfer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/8d/70929dde76e24f252d6cf1fb3224ff5694ca96451d9e7023a43555fab760/boto3-1.40.56.tar.gz", hash = "sha256:c1afdb04dd27418fc58400434ab8e05998bb452b69c428168d9ada344fe6b93e", size = 111489, upload-time = "2025-10-21T20:31:01.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/b0/0ce2afc7ed21ea815208a03af193c891d3971b96bc7ba93dd8569597951c/boto3-1.40.56-py3-none-any.whl", hash = "sha256:8985a840d57671aa3c6124b0c178e79be97e3447de4b5819156071793f82ee5c", size = 139322, upload-time = "2025-10-21T20:30:59.436Z" }, +] + +[[package]] +name = "botocore" +version = "1.40.56" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/03/e48e32cd73a7f82bae267320f435526bb6c7ec8d3d72d69febd4ec5b8ee9/botocore-1.40.56.tar.gz", hash = "sha256:b29df3418a299609632cab240ee79275463b176ebeb3adc841ba367a3fa0c4db", size = 14448556, upload-time = "2025-10-21T20:30:50.104Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/1d/b9e8f8fa7dae2e2d51c0c23bd5bcbd94c930241de7a6fa215ffac0dfaf16/botocore-1.40.56-py3-none-any.whl", hash = "sha256:0962dfc9bfb0afa1855042a88a72cc722cc7f9c08f51d2c5c88181d525a59a27", size = 14120124, upload-time = "2025-10-21T20:30:46.978Z" }, +] + +[[package]] +name = "brotli" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/c2/f9e977608bdf958650638c3f1e28f85a1b075f075ebbe77db8555463787b/Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724", size = 7372270, upload-time = "2023-09-07T14:05:41.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/12/ad41e7fadd5db55459c4c401842b47f7fee51068f86dd2894dd0dcfc2d2a/Brotli-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3daabb76a78f829cafc365531c972016e4aa8d5b4bf60660ad8ecee19df7ccc", size = 873068, upload-time = "2023-09-07T14:03:37.779Z" }, + { url = "https://files.pythonhosted.org/packages/95/4e/5afab7b2b4b61a84e9c75b17814198ce515343a44e2ed4488fac314cd0a9/Brotli-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c8146669223164fc87a7e3de9f81e9423c67a79d6b3447994dfb9c95da16e2d6", size = 446244, upload-time = "2023-09-07T14:03:39.223Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e6/f305eb61fb9a8580c525478a4a34c5ae1a9bcb12c3aee619114940bc513d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30924eb4c57903d5a7526b08ef4a584acc22ab1ffa085faceb521521d2de32dd", size = 2906500, upload-time = "2023-09-07T14:03:40.858Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4f/af6846cfbc1550a3024e5d3775ede1e00474c40882c7bf5b37a43ca35e91/Brotli-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ceb64bbc6eac5a140ca649003756940f8d6a7c444a68af170b3187623b43bebf", size = 2943950, upload-time = "2023-09-07T14:03:42.896Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e7/ca2993c7682d8629b62630ebf0d1f3bb3d579e667ce8e7ca03a0a0576a2d/Brotli-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a469274ad18dc0e4d316eefa616d1d0c2ff9da369af19fa6f3daa4f09671fd61", size = 2918527, upload-time = "2023-09-07T14:03:44.552Z" }, + { url = "https://files.pythonhosted.org/packages/b3/96/da98e7bedc4c51104d29cc61e5f449a502dd3dbc211944546a4cc65500d3/Brotli-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:524f35912131cc2cabb00edfd8d573b07f2d9f21fa824bd3fb19725a9cf06327", size = 2845489, upload-time = "2023-09-07T14:03:46.594Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/ccbc16947d6ce943a7f57e1a40596c75859eeb6d279c6994eddd69615265/Brotli-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5b3cc074004d968722f51e550b41a27be656ec48f8afaeeb45ebf65b561481dd", size = 2914080, upload-time = "2023-09-07T14:03:48.204Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/0bd38d758d1afa62a5524172f0b18626bb2392d717ff94806f741fcd5ee9/Brotli-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:19c116e796420b0cee3da1ccec3b764ed2952ccfcc298b55a10e5610ad7885f9", size = 2813051, upload-time = "2023-09-07T14:03:50.348Z" }, + { url = "https://files.pythonhosted.org/packages/14/56/48859dd5d129d7519e001f06dcfbb6e2cf6db92b2702c0c2ce7d97e086c1/Brotli-1.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:510b5b1bfbe20e1a7b3baf5fed9e9451873559a976c1a78eebaa3b86c57b4265", size = 2938172, upload-time = "2023-09-07T14:03:52.395Z" }, + { url = "https://files.pythonhosted.org/packages/3d/77/a236d5f8cd9e9f4348da5acc75ab032ab1ab2c03cc8f430d24eea2672888/Brotli-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a1fd8a29719ccce974d523580987b7f8229aeace506952fa9ce1d53a033873c8", size = 2933023, upload-time = "2023-09-07T14:03:53.96Z" }, + { url = "https://files.pythonhosted.org/packages/f1/87/3b283efc0f5cb35f7f84c0c240b1e1a1003a5e47141a4881bf87c86d0ce2/Brotli-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c247dd99d39e0338a604f8c2b3bc7061d5c2e9e2ac7ba9cc1be5a69cb6cd832f", size = 2935871, upload-time = "2024-10-18T12:32:16.688Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/2be4cc3e2141dc1a43ad4ca1875a72088229de38c68e842746b342667b2a/Brotli-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1b2c248cd517c222d89e74669a4adfa5577e06ab68771a529060cf5a156e9757", size = 2847784, upload-time = "2024-10-18T12:32:18.459Z" }, + { url = "https://files.pythonhosted.org/packages/66/13/b58ddebfd35edde572ccefe6890cf7c493f0c319aad2a5badee134b4d8ec/Brotli-1.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:2a24c50840d89ded6c9a8fdc7b6ed3692ed4e86f1c4a4a938e1e92def92933e0", size = 3034905, upload-time = "2024-10-18T12:32:20.192Z" }, + { url = "https://files.pythonhosted.org/packages/84/9c/bc96b6c7db824998a49ed3b38e441a2cae9234da6fa11f6ed17e8cf4f147/Brotli-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f31859074d57b4639318523d6ffdca586ace54271a73ad23ad021acd807eb14b", size = 2929467, upload-time = "2024-10-18T12:32:21.774Z" }, + { url = "https://files.pythonhosted.org/packages/e7/71/8f161dee223c7ff7fea9d44893fba953ce97cf2c3c33f78ba260a91bcff5/Brotli-1.1.0-cp311-cp311-win32.whl", hash = "sha256:39da8adedf6942d76dc3e46653e52df937a3c4d6d18fdc94a7c29d263b1f5b50", size = 333169, upload-time = "2023-09-07T14:03:55.404Z" }, + { url = "https://files.pythonhosted.org/packages/02/8a/fece0ee1057643cb2a5bbf59682de13f1725f8482b2c057d4e799d7ade75/Brotli-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:aac0411d20e345dc0920bdec5548e438e999ff68d77564d5e9463a7ca9d3e7b1", size = 357253, upload-time = "2023-09-07T14:03:56.643Z" }, + { url = "https://files.pythonhosted.org/packages/5c/d0/5373ae13b93fe00095a58efcbce837fd470ca39f703a235d2a999baadfbc/Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28", size = 815693, upload-time = "2024-10-18T12:32:23.824Z" }, + { url = "https://files.pythonhosted.org/packages/8e/48/f6e1cdf86751300c288c1459724bfa6917a80e30dbfc326f92cea5d3683a/Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f", size = 422489, upload-time = "2024-10-18T12:32:25.641Z" }, + { url = "https://files.pythonhosted.org/packages/06/88/564958cedce636d0f1bed313381dfc4b4e3d3f6015a63dae6146e1b8c65c/Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409", size = 873081, upload-time = "2023-09-07T14:03:57.967Z" }, + { url = "https://files.pythonhosted.org/packages/58/79/b7026a8bb65da9a6bb7d14329fd2bd48d2b7f86d7329d5cc8ddc6a90526f/Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2", size = 446244, upload-time = "2023-09-07T14:03:59.319Z" }, + { url = "https://files.pythonhosted.org/packages/e5/18/c18c32ecea41b6c0004e15606e274006366fe19436b6adccc1ae7b2e50c2/Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451", size = 2906505, upload-time = "2023-09-07T14:04:01.327Z" }, + { url = "https://files.pythonhosted.org/packages/08/c8/69ec0496b1ada7569b62d85893d928e865df29b90736558d6c98c2031208/Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91", size = 2944152, upload-time = "2023-09-07T14:04:03.033Z" }, + { url = "https://files.pythonhosted.org/packages/ab/fb/0517cea182219d6768113a38167ef6d4eb157a033178cc938033a552ed6d/Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408", size = 2919252, upload-time = "2023-09-07T14:04:04.675Z" }, + { url = "https://files.pythonhosted.org/packages/c7/53/73a3431662e33ae61a5c80b1b9d2d18f58dfa910ae8dd696e57d39f1a2f5/Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0", size = 2845955, upload-time = "2023-09-07T14:04:06.585Z" }, + { url = "https://files.pythonhosted.org/packages/55/ac/bd280708d9c5ebdbf9de01459e625a3e3803cce0784f47d633562cf40e83/Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc", size = 2914304, upload-time = "2023-09-07T14:04:08.668Z" }, + { url = "https://files.pythonhosted.org/packages/76/58/5c391b41ecfc4527d2cc3350719b02e87cb424ef8ba2023fb662f9bf743c/Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180", size = 2814452, upload-time = "2023-09-07T14:04:10.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/91b8256dfe99c407f174924b65a01f5305e303f486cc7a2e8a5d43c8bec3/Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248", size = 2938751, upload-time = "2023-09-07T14:04:12.875Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a6/e2a39a5d3b412938362bbbeba5af904092bf3f95b867b4a3eb856104074e/Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966", size = 2933757, upload-time = "2023-09-07T14:04:14.551Z" }, + { url = "https://files.pythonhosted.org/packages/13/f0/358354786280a509482e0e77c1a5459e439766597d280f28cb097642fc26/Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9", size = 2936146, upload-time = "2024-10-18T12:32:27.257Z" }, + { url = "https://files.pythonhosted.org/packages/80/f7/daf538c1060d3a88266b80ecc1d1c98b79553b3f117a485653f17070ea2a/Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb", size = 2848055, upload-time = "2024-10-18T12:32:29.376Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0eaa0585c4077d3c2d1edf322d8e97aabf317941d3a72d7b3ad8bce004b0/Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111", size = 3035102, upload-time = "2024-10-18T12:32:31.371Z" }, + { url = "https://files.pythonhosted.org/packages/d8/63/1c1585b2aa554fe6dbce30f0c18bdbc877fa9a1bf5ff17677d9cca0ac122/Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839", size = 2930029, upload-time = "2024-10-18T12:32:33.293Z" }, + { url = "https://files.pythonhosted.org/packages/5f/3b/4e3fd1893eb3bbfef8e5a80d4508bec17a57bb92d586c85c12d28666bb13/Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0", size = 333276, upload-time = "2023-09-07T14:04:16.49Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d5/942051b45a9e883b5b6e98c041698b1eb2012d25e5948c58d6bf85b1bb43/Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951", size = 357255, upload-time = "2023-09-07T14:04:17.83Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9f/fb37bb8ffc52a8da37b1c03c459a8cd55df7a57bdccd8831d500e994a0ca/Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5", size = 815681, upload-time = "2024-10-18T12:32:34.942Z" }, + { url = "https://files.pythonhosted.org/packages/06/b3/dbd332a988586fefb0aa49c779f59f47cae76855c2d00f450364bb574cac/Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8", size = 422475, upload-time = "2024-10-18T12:32:36.485Z" }, + { url = "https://files.pythonhosted.org/packages/bb/80/6aaddc2f63dbcf2d93c2d204e49c11a9ec93a8c7c63261e2b4bd35198283/Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f", size = 2906173, upload-time = "2024-10-18T12:32:37.978Z" }, + { url = "https://files.pythonhosted.org/packages/ea/1d/e6ca79c96ff5b641df6097d299347507d39a9604bde8915e76bf026d6c77/Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648", size = 2943803, upload-time = "2024-10-18T12:32:39.606Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a3/d98d2472e0130b7dd3acdbb7f390d478123dbf62b7d32bda5c830a96116d/Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0", size = 2918946, upload-time = "2024-10-18T12:32:41.679Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a5/c69e6d272aee3e1423ed005d8915a7eaa0384c7de503da987f2d224d0721/Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089", size = 2845707, upload-time = "2024-10-18T12:32:43.478Z" }, + { url = "https://files.pythonhosted.org/packages/58/9f/4149d38b52725afa39067350696c09526de0125ebfbaab5acc5af28b42ea/Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368", size = 2936231, upload-time = "2024-10-18T12:32:45.224Z" }, + { url = "https://files.pythonhosted.org/packages/5a/5a/145de884285611838a16bebfdb060c231c52b8f84dfbe52b852a15780386/Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c", size = 2848157, upload-time = "2024-10-18T12:32:46.894Z" }, + { url = "https://files.pythonhosted.org/packages/50/ae/408b6bfb8525dadebd3b3dd5b19d631da4f7d46420321db44cd99dcf2f2c/Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284", size = 3035122, upload-time = "2024-10-18T12:32:48.844Z" }, + { url = "https://files.pythonhosted.org/packages/af/85/a94e5cfaa0ca449d8f91c3d6f78313ebf919a0dbd55a100c711c6e9655bc/Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7", size = 2930206, upload-time = "2024-10-18T12:32:51.198Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f0/a61d9262cd01351df22e57ad7c34f66794709acab13f34be2675f45bf89d/Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0", size = 333804, upload-time = "2024-10-18T12:32:52.661Z" }, + { url = "https://files.pythonhosted.org/packages/7e/c1/ec214e9c94000d1c1974ec67ced1c970c148aa6b8d8373066123fc3dbf06/Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b", size = 358517, upload-time = "2024-10-18T12:32:54.066Z" }, +] + +[[package]] +name = "browser-use" +version = "0.1.48" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "faiss-cpu" }, + { name = "google-api-core" }, + { name = "httpx" }, + { name = "langchain" }, + { name = "langchain-anthropic" }, + { name = "langchain-aws" }, + { name = "langchain-core" }, + { name = "langchain-deepseek" }, + { name = "langchain-google-genai" }, + { name = "langchain-ollama" }, + { name = "langchain-openai" }, + { name = "markdownify" }, + { name = "mem0ai" }, + { name = "playwright" }, + { name = "posthog" }, + { name = "psutil" }, + { name = "pydantic" }, + { name = "pyobjc", marker = "platform_system == 'darwin'" }, + { name = "pyperclip" }, + { name = "python-dotenv" }, + { name = "requests" }, + { name = "screeninfo", marker = "platform_system != 'darwin'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/a0/8b4c08da6adc8be7bee48d216fbf829bb7f5f9cd5c06147ee9d0da11593a/browser_use-0.1.48.tar.gz", hash = "sha256:7c061c8fdea735345d6d480d7c7fd2b24557826fa92c00d8efd7f98f4d6f29c1", size = 127897, upload-time = "2025-05-15T22:47:33.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/ea/527e3c2108b78517a5b952b20039dbe46e90ca297222462989fc9bc85a51/browser_use-0.1.48-py3-none-any.whl", hash = "sha256:7848ac2cd35d0b8b0528d4b8c44dc637ce3efce73b29ca1c41f3bd1f7845de40", size = 146023, upload-time = "2025-05-15T22:47:31.901Z" }, +] + +[[package]] +name = "cachetools" +version = "6.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/7e/b975b5814bd36faf009faebe22c1072a1fa1168db34d285ef0ba071ad78c/cachetools-6.2.1.tar.gz", hash = "sha256:3f391e4bd8f8bf0931169baf7456cc822705f4e2a31f840d218f445b9a854201", size = 31325, upload-time = "2025-10-12T14:55:30.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/c5/1e741d26306c42e2bf6ab740b2202872727e0f606033c9dd713f8b93f5a8/cachetools-6.2.1-py3-none-any.whl", hash = "sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701", size = 11280, upload-time = "2025-10-12T14:55:28.382Z" }, +] + +[[package]] +name = "certifi" +version = "2025.10.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/5b/b6ce21586237c77ce67d01dc5507039d444b630dd76611bbca2d8e5dcd91/certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43", size = 164519, upload-time = "2025-10-05T04:12:15.808Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/37/af0d2ef3967ac0d6113837b44a4f0bfe1328c2b9763bd5b1744520e5cfed/certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", size = 163286, upload-time = "2025-10-05T04:12:14.03Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, + { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, + { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, + { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, + { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, + { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, + { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, + { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "courlan" +version = "1.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "tld" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/54/6d6ceeff4bed42e7a10d6064d35ee43a810e7b3e8beb4abeae8cff4713ae/courlan-1.3.2.tar.gz", hash = "sha256:0b66f4db3a9c39a6e22dd247c72cfaa57d68ea660e94bb2c84ec7db8712af190", size = 206382, upload-time = "2024-10-29T16:40:20.994Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/ca/6a667ccbe649856dcd3458bab80b016681b274399d6211187c6ab969fc50/courlan-1.3.2-py3-none-any.whl", hash = "sha256:d0dab52cf5b5b1000ee2839fbc2837e93b2514d3cb5bb61ae158a55b7a04c6be", size = 33848, upload-time = "2024-10-29T16:40:18.325Z" }, +] + +[[package]] +name = "cython" +version = "3.1.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/ab/4e980fbfbc894f95854aabff68a029dd6044a9550c480a1049a65263c72b/cython-3.1.5.tar.gz", hash = "sha256:7e73c7e6da755a8dffb9e0e5c4398e364e37671778624188444f1ff0d9458112", size = 3192050, upload-time = "2025-10-20T06:06:51.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/f3/fcd5a3c43db19884dfafe7794b463728c70147aa1876223f431916d44984/cython-3.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1aad56376c6ff10deee50f3a9ff5a1fddbe24c6debad7041b86cc618f127836a", size = 3026477, upload-time = "2025-10-20T06:09:07.712Z" }, + { url = "https://files.pythonhosted.org/packages/3d/19/81fa80bdeca5cee456ac52728c993e62eaf58407d19232db55536cf66c4b/cython-3.1.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ef1df5201bf6eef6224e04584b0032874bd1e10e9f4e5701bfa502fca2f301bb", size = 2956078, upload-time = "2025-10-20T06:09:09.781Z" }, + { url = "https://files.pythonhosted.org/packages/54/3c/beb8bd4b94ae08cc9b90aac152e917e2fcab1d3189fb5143bc5f1622dc59/cython-3.1.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:38bf7bbe29e8508645d2c3d6313f7fb6872c22f54980f68819422d0812c95f69", size = 3063044, upload-time = "2025-10-20T06:09:32.361Z" }, + { url = "https://files.pythonhosted.org/packages/3b/88/1e0df92588704503a863230fed61d95fc6e38c0db2537eaf6e5c140e5055/cython-3.1.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61c42f881320a2b34a88806ddee6b424b3caa6fa193b008123704a2896b5bc37", size = 2970800, upload-time = "2025-10-20T06:09:34.58Z" }, + { url = "https://files.pythonhosted.org/packages/89/7e/9b4e099076e6a56939ef7def0ebf7f31f204fc2383be57f31fd0d8c91659/cython-3.1.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3c9b6d424f8b4f621b2d08ee5c344970311df0dac5c259667786b21b77657460", size = 3051579, upload-time = "2025-10-20T06:09:54.733Z" }, + { url = "https://files.pythonhosted.org/packages/a4/4d/4f5d2ab95ed507f8c510bf8044d9d07b44ad1e0a684b3b8796c9003e39ef/cython-3.1.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:08e998a4d5049ea75932674701fa283397477330d1583bc9f63b693a380a38c6", size = 2958963, upload-time = "2025-10-20T06:09:56.45Z" }, + { url = "https://files.pythonhosted.org/packages/7c/52/a44f5b3e7988ef3a55ea297cd5b56204ff5d0caaf7df048bcb78efe595ab/cython-3.1.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:888bf3f12aadfb2dc2c41e83932f40fc2ac519933c809aae16e901c4413d6966", size = 3046849, upload-time = "2025-10-20T06:10:14.087Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a8/fb84d9b6cc933b65f4e3cedc4e69a1baa7987f6dfb5165f89298521c2073/cython-3.1.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85ffc5aa27d2e175bab4c649299aa4ae2b4c559040a5bf50b0ad141e76e17032", size = 2967186, upload-time = "2025-10-20T06:10:16.286Z" }, + { url = "https://files.pythonhosted.org/packages/1b/33/8af1a1d424176a5f8710b687b84dd2f403e41b87b0e0acf569d39723f257/cython-3.1.5-py3-none-any.whl", hash = "sha256:1bef4a168f4f650d17d67b43792ed045829b570f1e4108c6c37a56fe268aa728", size = 1227619, upload-time = "2025-10-20T06:06:48.387Z" }, +] + +[[package]] +name = "dataclasses-json" +version = "0.6.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "marshmallow" }, + { name = "typing-inspect" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/a4/f71d9cf3a5ac257c993b5ca3f93df5f7fb395c725e7f1e6479d2514173c3/dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0", size = 32227, upload-time = "2024-06-09T16:20:19.103Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/be/d0d44e092656fe7a06b55e6103cbce807cdbdee17884a5367c68c9860853/dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a", size = 28686, upload-time = "2024-06-09T16:20:16.715Z" }, +] + +[[package]] +name = "dateparser" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "regex" }, + { name = "tzlocal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/30/064144f0df1749e7bb5faaa7f52b007d7c2d08ec08fed8411aba87207f68/dateparser-1.2.2.tar.gz", hash = "sha256:986316f17cb8cdc23ea8ce563027c5ef12fc725b6fb1d137c14ca08777c5ecf7", size = 329840, upload-time = "2025-06-26T09:29:23.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/22/f020c047ae1346613db9322638186468238bcfa8849b4668a22b97faad65/dateparser-1.2.2-py3-none-any.whl", hash = "sha256:5a5d7211a09013499867547023a2a0c91d5a27d15dd4dbcea676ea9fe66f2482", size = 315453, upload-time = "2025-06-26T09:29:21.412Z" }, +] + +[[package]] +name = "defusedxml" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "docstring-parser" +version = "0.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" }, +] + +[[package]] +name = "faiss-cpu" +version = "1.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/80/bb75a7ed6e824dea452a24d3434a72ed799324a688b10b047d441d270185/faiss_cpu-1.12.0.tar.gz", hash = "sha256:2f87cbcd603f3ed464ebceb857971fdebc318de938566c9ae2b82beda8e953c0", size = 69292, upload-time = "2025-08-13T06:07:26.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/ed/83fed257ea410c2e691374f04ac914d5f9414f04a9c7a266bdfbb999eb16/faiss_cpu-1.12.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:fbb63595c7ad43c0d9caaf4d554a38a30ea4edda5e7c3ed38845562776992ba9", size = 8006079, upload-time = "2025-08-13T06:05:48.932Z" }, + { url = "https://files.pythonhosted.org/packages/5b/07/80c248db87ef2e753ad390fca3b0d7dd6092079e904f35b248c7064e791e/faiss_cpu-1.12.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:83e74cbde6fa5caceec5bc103c82053d50fde163e3ceabaa58c91508e984142b", size = 3360138, upload-time = "2025-08-13T06:05:50.873Z" }, + { url = "https://files.pythonhosted.org/packages/b9/22/73bd9ed7b11cd14eb0da6e2f2eae763306abaad1b25a5808da8b1fc07665/faiss_cpu-1.12.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6155a5138604b702a32f8f0a63948a539eb7468898554a9911f9ab8c899284fb", size = 3825466, upload-time = "2025-08-13T06:05:52.311Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7f/e1a21337b3cba24b953c760696e3b188a533d724440e050fd60a3c1aa919/faiss_cpu-1.12.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bf4b5f0e9b6bb5a566b1a31e84a93b283f26c2b0155fb2eb5970c32a540a906", size = 31425626, upload-time = "2025-08-13T06:05:54.155Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/f352cf8400f414e6a31385ef12d43d11aac8beb11d573a2fd00ec44b8cb7/faiss_cpu-1.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:60a535b79d3d6225c7c21d7277fb0c6fde80c46a9c1e33632b1b293c1d177f30", size = 9751949, upload-time = "2025-08-13T06:05:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/05/50/a122e3076d7fd95cbe9a0cdf0fc796836f1e4fd399b418c6ba8533c75770/faiss_cpu-1.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0d1b243468a24564f85a41166f2ca4c92f8f6755da096ffbdcf551675ca739c5", size = 24161021, upload-time = "2025-08-13T06:05:58.776Z" }, + { url = "https://files.pythonhosted.org/packages/72/9f/3344f6fe69f6fbfb19dec298b4dda3d47a87dc31e418911fdcc3a3ace013/faiss_cpu-1.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:84510079a2efe954e6b89fe5e62f23a98c1ef999756565e056f95f835ff43c5e", size = 18169278, upload-time = "2025-08-13T06:06:01.44Z" }, + { url = "https://files.pythonhosted.org/packages/4c/b1/37d532292c1b3dab690636947a532d3797741b09f2dfb9cb558ffeaff34b/faiss_cpu-1.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:2283f1014f7f86dd56b53bf0ea0d7f848eb4c9c6704b8f4f99a0af02e994e479", size = 8007093, upload-time = "2025-08-13T06:06:03.904Z" }, + { url = "https://files.pythonhosted.org/packages/4a/58/602ed184d35742eb240cbfea237bd214f2ae7f01cb369c39f4dff392f7c9/faiss_cpu-1.12.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:9b54990fcbcf90e37393909d4033520237194263c93ab6dbfae0616ef9af242b", size = 8034413, upload-time = "2025-08-13T06:06:05.564Z" }, + { url = "https://files.pythonhosted.org/packages/83/d5/f84c3d0e022cdeb73ff8406a6834a7698829fa242eb8590ddf8a0b09357f/faiss_cpu-1.12.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a5f5bca7e1a3e0a98480d1e2748fc86d12c28d506173e460e6746886ff0e08de", size = 3362034, upload-time = "2025-08-13T06:06:07.091Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a4ba4d285ea4f9b0824bf31ebded3171da08bfcf5376f4771cc5481f72cd/faiss_cpu-1.12.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:016e391f49933875b8d60d47f282f2e93d8ea9f9ffbda82467aa771b11a237db", size = 3834319, upload-time = "2025-08-13T06:06:08.86Z" }, + { url = "https://files.pythonhosted.org/packages/4c/c9/be4e52fd96be601fefb313c26e1259ac2e6b556fb08cc392db641baba8c7/faiss_cpu-1.12.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2e4963c7188f57cfba248f09ebd8a14c76b5ffb87382603ccd4576f2da39d74", size = 31421585, upload-time = "2025-08-13T06:06:10.643Z" }, + { url = "https://files.pythonhosted.org/packages/4b/aa/12c6723ce30df721a6bace21398559c0367c5418c04139babc2d26d8d158/faiss_cpu-1.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:88bfe134f8c7cd2dda7df34f2619448906624962c8207efdd6eb1647e2f5338b", size = 9762449, upload-time = "2025-08-13T06:06:13.373Z" }, + { url = "https://files.pythonhosted.org/packages/67/15/ed2c9de47c3ebae980d6938f0ec12d739231438958bc5ab2d636b272d913/faiss_cpu-1.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9243ee4c224a0d74419040503f22bf067462a040281bf6f3f107ab205c97d438", size = 24156525, upload-time = "2025-08-13T06:06:15.307Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b8/6911de6b8fdcfa76144680c2195df6ce7e0cc920a8be8c5bbd2dfe5e3c37/faiss_cpu-1.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:6b8012353d50d9bc81bcfe35b226d0e5bfad345fdebe0da31848395ebc83816d", size = 18169636, upload-time = "2025-08-13T06:06:17.613Z" }, + { url = "https://files.pythonhosted.org/packages/2f/69/d2b0f434b0ae35344280346b58d2b9a251609333424f3289c54506e60c51/faiss_cpu-1.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:8b4f5b18cbe335322a51d2785bb044036609c35bfac5915bff95eadc10e89ef1", size = 8012423, upload-time = "2025-08-13T06:06:19.73Z" }, + { url = "https://files.pythonhosted.org/packages/5f/4e/6be5fbd2ceccd87b168c64edeefa469cd11f095bb63b16a61a29296b0fdb/faiss_cpu-1.12.0-cp313-cp313-macosx_13_0_x86_64.whl", hash = "sha256:c9c79b5f28dcf9b2e2557ce51b938b21b7a9d508e008dc1ffea7b8249e7bd443", size = 8034409, upload-time = "2025-08-13T06:06:22.519Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f0/658012a91a690d82f3587fd8e56ea1d9b9698c31970929a9dba17edd211e/faiss_cpu-1.12.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0db6485bc9f32b69aaccf9ad520782371a79904dcfe20b6da5cbfd61a712e85f", size = 3362034, upload-time = "2025-08-13T06:06:24.052Z" }, + { url = "https://files.pythonhosted.org/packages/81/8b/9b355309d448e1a737fac31d45e9b2484ffb0f04f10fba3b544efe6661e4/faiss_cpu-1.12.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6db5532831791d7bac089fc580e741e99869122946bb6a5f120016c83b95d10", size = 3834324, upload-time = "2025-08-13T06:06:25.506Z" }, + { url = "https://files.pythonhosted.org/packages/7e/31/d229f6cdb9cbe03020499d69c4b431b705aa19a55aa0fe698c98022b2fef/faiss_cpu-1.12.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d57ed7aac048b18809af70350c31acc0fb9f00e6c03b6ed1651fd58b174882d", size = 31421590, upload-time = "2025-08-13T06:06:27.601Z" }, + { url = "https://files.pythonhosted.org/packages/26/19/80289ba008f14c95fbb6e94617ea9884e421ca745864fe6b8b90e1c3fc94/faiss_cpu-1.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:26c29290e7d1c5938e5886594dc0a2272b30728351ca5f855d4ae30704d5a6cc", size = 9762452, upload-time = "2025-08-13T06:06:30.237Z" }, + { url = "https://files.pythonhosted.org/packages/af/e7/6cc03ead5e19275e34992419e2b7d107d0295390ccf589636ff26adb41e2/faiss_cpu-1.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b43d0c295e93a8e5f1dd30325caaf34d4ecb51f1e3d461c7b0e71bff3a8944b", size = 24156530, upload-time = "2025-08-13T06:06:32.23Z" }, + { url = "https://files.pythonhosted.org/packages/34/90/438865fe737d65e7348680dadf3b2983bdcef7e5b7e852000e74c50a9933/faiss_cpu-1.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:a7c6156f1309bb969480280906e8865c3c4378eebb0f840c55c924bf06efd8d3", size = 18169604, upload-time = "2025-08-13T06:06:34.884Z" }, + { url = "https://files.pythonhosted.org/packages/76/69/40a1d8d781a70d33c57ef1b4b777486761dd1c502a86d27e90ef6aa8a9f9/faiss_cpu-1.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:0b5fac98a350774a98b904f7a7c6689eb5cf0a593d63c552e705a80c55636d15", size = 8012523, upload-time = "2025-08-13T06:06:37.24Z" }, + { url = "https://files.pythonhosted.org/packages/12/35/01a4a7c179d67bee0d8a027b95c3eae19cb354ae69ef2bc50ac3b93bc853/faiss_cpu-1.12.0-cp314-cp314-macosx_13_0_x86_64.whl", hash = "sha256:ff7db774968210d08cd0331287f3f66a6ffef955a7aa9a7fcd3eb4432a4ce5f5", size = 8036142, upload-time = "2025-08-13T06:06:38.894Z" }, + { url = "https://files.pythonhosted.org/packages/08/23/bac2859490096608c9d527f3041b44c2e43f8df0d4aadd53a4cc5ce678ac/faiss_cpu-1.12.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:220b5bb5439c64e417b35f9ade4c7dc3bf7df683d6123901ba84d6d764ecd486", size = 3363747, upload-time = "2025-08-13T06:06:40.73Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/e18023e1f43a18ec593adcd69d356f1fa94bde20344e38334d5985e5c5cc/faiss_cpu-1.12.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693d0bf16f79e8d16a1baaeda459f3375f37da0354e97dc032806b48a2a54151", size = 3835232, upload-time = "2025-08-13T06:06:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/1c1fea423d3f550f44c5ec3f14d8400919b49c285c3bd146687c63e40186/faiss_cpu-1.12.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bcc6587dee21e17430fb49ddc5200625d6f5e1de2bdf436f14827bad4ca78d19", size = 31432677, upload-time = "2025-08-13T06:06:44.348Z" }, + { url = "https://files.pythonhosted.org/packages/de/d2/3483e92a02f30e2d8491a256f470f54b7f5483266dfe09126d28741d31ec/faiss_cpu-1.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b80e5965f001822cc99ec65c715169af1b70bdae72eccd573520a2dec485b3ee", size = 9765504, upload-time = "2025-08-13T06:06:46.567Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2f/d97792211a9bd84b8d6b1dcaa1dcd69ac11e026c6ef19c641b6a87e31025/faiss_cpu-1.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98279f1b4876ef9902695a329b81a99002782ab6e26def472022009df6f1ac68", size = 24169930, upload-time = "2025-08-13T06:06:48.916Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b8/b707ca4d88af472509a053c39d3cced53efd19d096b8dff2fadc18c4b82d/faiss_cpu-1.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:11670337f9f5ee9ff3490e30683eea80add060c300cf6f6cb0e8faf3155fd20e", size = 18475400, upload-time = "2025-08-13T06:06:51.233Z" }, + { url = "https://files.pythonhosted.org/packages/77/11/42e41ddebde4dfe77e36e92d0110b4f733c8640883abffde54f802482deb/faiss_cpu-1.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:7ac1c8b53609b5c722ab60f1749260a7cb3c72fdfb720a0e3033067e73591da5", size = 8281229, upload-time = "2025-08-13T06:06:53.735Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9a/8ae5bbeabe70eb673c37fc7c77e2e476746331afb6654b2df97d8b6d380d/faiss_cpu-1.12.0-cp314-cp314t-macosx_13_0_x86_64.whl", hash = "sha256:110b21b7bb4c93c4f1a5eb2ffb8ef99dcdb4725f8ab2e5cd161324e4d981f204", size = 8087247, upload-time = "2025-08-13T06:06:55.407Z" }, + { url = "https://files.pythonhosted.org/packages/f4/df/b3d79098860b67b126da351788c04ac243c29718dadc4a678a6f5e7209c0/faiss_cpu-1.12.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:82eb5515ce72be9a43f4cf74447a0d090e014231981df91aff7251204b506fbf", size = 3411043, upload-time = "2025-08-13T06:06:56.983Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2f/b1a2a03dd3cce22ff9fc434aa3c7390125087260c1d1349311da36eaa432/faiss_cpu-1.12.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:754eef89cdf2b35643df6b0923a5a098bdfecf63b5f4bd86c385042ee511b287", size = 3801789, upload-time = "2025-08-13T06:06:58.688Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a8/16ad0c6a966e93d04bfd5248d2be1d8b5849842b0e2611c5ecd26fcaf036/faiss_cpu-1.12.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7285c71c8f5e9c58b55175f5f74c78c518c52c421a88a430263f34e3e31f719c", size = 31231388, upload-time = "2025-08-13T06:07:00.55Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/9c16eca0b8f8b13c32c47a5e4ff7a4bc0ca3e7d263140312088811230871/faiss_cpu-1.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:84a50d7a2f711f79cc8b65aa28956dba6435e47b71a38b2daea44c94c9b8e458", size = 9737605, upload-time = "2025-08-13T06:07:03.018Z" }, + { url = "https://files.pythonhosted.org/packages/a8/4a/2c2d615078c9d816a836fb893aaef551ad152f2eb00bc258698273c240c0/faiss_cpu-1.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f3e0a14e4edec6a3959a9f51afccb89e863138f184ff2cc24c13f9ad788740b", size = 23922880, upload-time = "2025-08-13T06:07:05.099Z" }, + { url = "https://files.pythonhosted.org/packages/30/aa/99b8402a4dac678794f13f8f4f29d666c2ef0a91594418147f47034ebc81/faiss_cpu-1.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8b3239cc371df6826ac43c62ac04eec7cc497bedb43f681fcd8ea494f520ddbb", size = 18750661, upload-time = "2025-08-13T06:07:07.551Z" }, + { url = "https://files.pythonhosted.org/packages/a3/a2/b546e9a20ba157eb2fbe141289f1752f157ee6d932899f4853df4ded6d4b/faiss_cpu-1.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58b23456db725ee1bd605a6135d2ef55b2ac3e0b6fe873fd99a909e8ef4bd0ff", size = 8302032, upload-time = "2025-08-13T06:07:09.602Z" }, +] + +[[package]] +name = "fastapi" +version = "0.119.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/f4/152127681182e6413e7a89684c434e19e7414ed7ac0c632999c3c6980640/fastapi-0.119.1.tar.gz", hash = "sha256:a5e3426edce3fe221af4e1992c6d79011b247e3b03cc57999d697fe76cbf8ae0", size = 338616, upload-time = "2025-10-20T11:30:27.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/26/e6d959b4ac959fdb3e9c4154656fc160794db6af8e64673d52759456bf07/fastapi-0.119.1-py3-none-any.whl", hash = "sha256:0b8c2a2cce853216e150e9bd4faaed88227f8eb37de21cb200771f491586a27f", size = 108123, upload-time = "2025-10-20T11:30:26.185Z" }, +] + +[[package]] +name = "ffmpy" +version = "0.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/dd/80760526c2742074c004e5a434665b577ddaefaedad51c5b8fa4526c77e0/ffmpy-0.6.3.tar.gz", hash = "sha256:306f3e9070e11a3da1aee3241d3a6bd19316ff7284716e15a1bc98d7a1939eaf", size = 4975, upload-time = "2025-10-11T07:34:56.609Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/50/e9409c94a0e9a9d1ec52c6f60e086c52aa0178a0f6f00d7f5e809a201179/ffmpy-0.6.3-py3-none-any.whl", hash = "sha256:f7b25c85a4075bf5e68f8b4eb0e332cb8f1584dfc2e444ff590851eaef09b286", size = 5495, upload-time = "2025-10-11T07:34:55.124Z" }, +] + +[[package]] +name = "filelock" +version = "3.20.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, +] + +[[package]] +name = "filetype" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/29/745f7d30d47fe0f251d3ad3dc2978a23141917661998763bebb6da007eb1/filetype-1.2.0.tar.gz", hash = "sha256:66b56cd6474bf41d8c54660347d37afcc3f7d1970648de365c102ef77548aadb", size = 998020, upload-time = "2022-11-02T17:34:04.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/79/1b8fa1bb3568781e84c9200f951c735f3f157429f44be0495da55894d620/filetype-1.2.0-py2.py3-none-any.whl", hash = "sha256:7ce71b6880181241cf7ac8697a2f1eb6a8bd9b429f7ad6d27b8db9ba5f1c2d25", size = 19970, upload-time = "2022-11-02T17:34:01.425Z" }, +] + +[[package]] +name = "frozenlist" +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/2d/40/0832c31a37d60f60ed79e9dfb5a92e1e2af4f40a16a29abcc7992af9edff/frozenlist-1.8.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d92f1a84bb12d9e56f818b3a746f3efba93c1b63c8387a73dde655e1e42282a", size = 85717, upload-time = "2025-10-06T05:36:27.341Z" }, + { url = "https://files.pythonhosted.org/packages/30/ba/b0b3de23f40bc55a7057bd38434e25c34fa48e17f20ee273bbde5e0650f3/frozenlist-1.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96153e77a591c8adc2ee805756c61f59fef4cf4073a9275ee86fe8cba41241f7", size = 49651, upload-time = "2025-10-06T05:36:28.855Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ab/6e5080ee374f875296c4243c381bbdef97a9ac39c6e3ce1d5f7d42cb78d6/frozenlist-1.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f21f00a91358803399890ab167098c131ec2ddd5f8f5fd5fe9c9f2c6fcd91e40", size = 49417, upload-time = "2025-10-06T05:36:29.877Z" }, + { url = "https://files.pythonhosted.org/packages/d5/4e/e4691508f9477ce67da2015d8c00acd751e6287739123113a9fca6f1604e/frozenlist-1.8.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fb30f9626572a76dfe4293c7194a09fb1fe93ba94c7d4f720dfae3b646b45027", size = 234391, upload-time = "2025-10-06T05:36:31.301Z" }, + { url = "https://files.pythonhosted.org/packages/40/76/c202df58e3acdf12969a7895fd6f3bc016c642e6726aa63bd3025e0fc71c/frozenlist-1.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eaa352d7047a31d87dafcacbabe89df0aa506abb5b1b85a2fb91bc3faa02d822", size = 233048, upload-time = "2025-10-06T05:36:32.531Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/8746afb90f17b73ca5979c7a3958116e105ff796e718575175319b5bb4ce/frozenlist-1.8.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:03ae967b4e297f58f8c774c7eabcce57fe3c2434817d4385c50661845a058121", size = 226549, upload-time = "2025-10-06T05:36:33.706Z" }, + { url = "https://files.pythonhosted.org/packages/7e/eb/4c7eefc718ff72f9b6c4893291abaae5fbc0c82226a32dcd8ef4f7a5dbef/frozenlist-1.8.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f6292f1de555ffcc675941d65fffffb0a5bcd992905015f85d0592201793e0e5", size = 239833, upload-time = "2025-10-06T05:36:34.947Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4e/e5c02187cf704224f8b21bee886f3d713ca379535f16893233b9d672ea71/frozenlist-1.8.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29548f9b5b5e3460ce7378144c3010363d8035cea44bc0bf02d57f5a685e084e", size = 245363, upload-time = "2025-10-06T05:36:36.534Z" }, + { url = "https://files.pythonhosted.org/packages/1f/96/cb85ec608464472e82ad37a17f844889c36100eed57bea094518bf270692/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ec3cc8c5d4084591b4237c0a272cc4f50a5b03396a47d9caaf76f5d7b38a4f11", size = 229314, upload-time = "2025-10-06T05:36:38.582Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6f/4ae69c550e4cee66b57887daeebe006fe985917c01d0fff9caab9883f6d0/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:517279f58009d0b1f2e7c1b130b377a349405da3f7621ed6bfae50b10adf20c1", size = 243365, upload-time = "2025-10-06T05:36:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/7a/58/afd56de246cf11780a40a2c28dc7cbabbf06337cc8ddb1c780a2d97e88d8/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:db1e72ede2d0d7ccb213f218df6a078a9c09a7de257c2fe8fcef16d5925230b1", size = 237763, upload-time = "2025-10-06T05:36:41.355Z" }, + { url = "https://files.pythonhosted.org/packages/cb/36/cdfaf6ed42e2644740d4a10452d8e97fa1c062e2a8006e4b09f1b5fd7d63/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b4dec9482a65c54a5044486847b8a66bf10c9cb4926d42927ec4e8fd5db7fed8", size = 240110, upload-time = "2025-10-06T05:36:42.716Z" }, + { url = "https://files.pythonhosted.org/packages/03/a8/9ea226fbefad669f11b52e864c55f0bd57d3c8d7eb07e9f2e9a0b39502e1/frozenlist-1.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:21900c48ae04d13d416f0e1e0c4d81f7931f73a9dfa0b7a8746fb2fe7dd970ed", size = 233717, upload-time = "2025-10-06T05:36:44.251Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0b/1b5531611e83ba7d13ccc9988967ea1b51186af64c42b7a7af465dcc9568/frozenlist-1.8.0-cp313-cp313-win32.whl", hash = "sha256:8b7b94a067d1c504ee0b16def57ad5738701e4ba10cec90529f13fa03c833496", size = 39628, upload-time = "2025-10-06T05:36:45.423Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cf/174c91dbc9cc49bc7b7aab74d8b734e974d1faa8f191c74af9b7e80848e6/frozenlist-1.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:878be833caa6a3821caf85eb39c5ba92d28e85df26d57afb06b35b2efd937231", size = 43882, upload-time = "2025-10-06T05:36:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/c1/17/502cd212cbfa96eb1388614fe39a3fc9ab87dbbe042b66f97acb57474834/frozenlist-1.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:44389d135b3ff43ba8cc89ff7f51f5a0bb6b63d829c8300f79a2fe4fe61bcc62", size = 39676, upload-time = "2025-10-06T05:36:47.8Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5c/3bbfaa920dfab09e76946a5d2833a7cbdf7b9b4a91c714666ac4855b88b4/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e25ac20a2ef37e91c1b39938b591457666a0fa835c7783c3a8f33ea42870db94", size = 89235, upload-time = "2025-10-06T05:36:48.78Z" }, + { url = "https://files.pythonhosted.org/packages/d2/d6/f03961ef72166cec1687e84e8925838442b615bd0b8854b54923ce5b7b8a/frozenlist-1.8.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07cdca25a91a4386d2e76ad992916a85038a9b97561bf7a3fd12d5d9ce31870c", size = 50742, upload-time = "2025-10-06T05:36:49.837Z" }, + { url = "https://files.pythonhosted.org/packages/1e/bb/a6d12b7ba4c3337667d0e421f7181c82dda448ce4e7ad7ecd249a16fa806/frozenlist-1.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4e0c11f2cc6717e0a741f84a527c52616140741cd812a50422f83dc31749fb52", size = 51725, upload-time = "2025-10-06T05:36:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/bc/71/d1fed0ffe2c2ccd70b43714c6cab0f4188f09f8a67a7914a6b46ee30f274/frozenlist-1.8.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b3210649ee28062ea6099cfda39e147fa1bc039583c8ee4481cb7811e2448c51", size = 284533, upload-time = "2025-10-06T05:36:51.898Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/fb1685a7b009d89f9bf78a42d94461bc06581f6e718c39344754a5d9bada/frozenlist-1.8.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:581ef5194c48035a7de2aefc72ac6539823bb71508189e5de01d60c9dcd5fa65", size = 292506, upload-time = "2025-10-06T05:36:53.101Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3b/b991fe1612703f7e0d05c0cf734c1b77aaf7c7d321df4572e8d36e7048c8/frozenlist-1.8.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3ef2d026f16a2b1866e1d86fc4e1291e1ed8a387b2c333809419a2f8b3a77b82", size = 274161, upload-time = "2025-10-06T05:36:54.309Z" }, + { url = "https://files.pythonhosted.org/packages/ca/ec/c5c618767bcdf66e88945ec0157d7f6c4a1322f1473392319b7a2501ded7/frozenlist-1.8.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5500ef82073f599ac84d888e3a8c1f77ac831183244bfd7f11eaa0289fb30714", size = 294676, upload-time = "2025-10-06T05:36:55.566Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ce/3934758637d8f8a88d11f0585d6495ef54b2044ed6ec84492a91fa3b27aa/frozenlist-1.8.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50066c3997d0091c411a66e710f4e11752251e6d2d73d70d8d5d4c76442a199d", size = 300638, upload-time = "2025-10-06T05:36:56.758Z" }, + { url = "https://files.pythonhosted.org/packages/fc/4f/a7e4d0d467298f42de4b41cbc7ddaf19d3cfeabaf9ff97c20c6c7ee409f9/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5c1c8e78426e59b3f8005e9b19f6ff46e5845895adbde20ece9218319eca6506", size = 283067, upload-time = "2025-10-06T05:36:57.965Z" }, + { url = "https://files.pythonhosted.org/packages/dc/48/c7b163063d55a83772b268e6d1affb960771b0e203b632cfe09522d67ea5/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:eefdba20de0d938cec6a89bd4d70f346a03108a19b9df4248d3cf0d88f1b0f51", size = 292101, upload-time = "2025-10-06T05:36:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/9f/d0/2366d3c4ecdc2fd391e0afa6e11500bfba0ea772764d631bbf82f0136c9d/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cf253e0e1c3ceb4aaff6df637ce033ff6535fb8c70a764a8f46aafd3d6ab798e", size = 289901, upload-time = "2025-10-06T05:37:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/b8/94/daff920e82c1b70e3618a2ac39fbc01ae3e2ff6124e80739ce5d71c9b920/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:032efa2674356903cd0261c4317a561a6850f3ac864a63fc1583147fb05a79b0", size = 289395, upload-time = "2025-10-06T05:37:02.115Z" }, + { url = "https://files.pythonhosted.org/packages/e3/20/bba307ab4235a09fdcd3cc5508dbabd17c4634a1af4b96e0f69bfe551ebd/frozenlist-1.8.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6da155091429aeba16851ecb10a9104a108bcd32f6c1642867eadaee401c1c41", size = 283659, upload-time = "2025-10-06T05:37:03.711Z" }, + { url = "https://files.pythonhosted.org/packages/fd/00/04ca1c3a7a124b6de4f8a9a17cc2fcad138b4608e7a3fc5877804b8715d7/frozenlist-1.8.0-cp313-cp313t-win32.whl", hash = "sha256:0f96534f8bfebc1a394209427d0f8a63d343c9779cda6fc25e8e121b5fd8555b", size = 43492, upload-time = "2025-10-06T05:37:04.915Z" }, + { url = "https://files.pythonhosted.org/packages/59/5e/c69f733a86a94ab10f68e496dc6b7e8bc078ebb415281d5698313e3af3a1/frozenlist-1.8.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5d63a068f978fc69421fb0e6eb91a9603187527c86b7cd3f534a5b77a592b888", size = 48034, upload-time = "2025-10-06T05:37:06.343Z" }, + { url = "https://files.pythonhosted.org/packages/16/6c/be9d79775d8abe79b05fa6d23da99ad6e7763a1d080fbae7290b286093fd/frozenlist-1.8.0-cp313-cp313t-win_arm64.whl", hash = "sha256:bf0a7e10b077bf5fb9380ad3ae8ce20ef919a6ad93b4552896419ac7e1d8e042", size = 41749, upload-time = "2025-10-06T05:37:07.431Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c8/85da824b7e7b9b6e7f7705b2ecaf9591ba6f79c1177f324c2735e41d36a2/frozenlist-1.8.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cee686f1f4cadeb2136007ddedd0aaf928ab95216e7691c63e50a8ec066336d0", size = 86127, upload-time = "2025-10-06T05:37:08.438Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e8/a1185e236ec66c20afd72399522f142c3724c785789255202d27ae992818/frozenlist-1.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:119fb2a1bd47307e899c2fac7f28e85b9a543864df47aa7ec9d3c1b4545f096f", size = 49698, upload-time = "2025-10-06T05:37:09.48Z" }, + { url = "https://files.pythonhosted.org/packages/a1/93/72b1736d68f03fda5fdf0f2180fb6caaae3894f1b854d006ac61ecc727ee/frozenlist-1.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4970ece02dbc8c3a92fcc5228e36a3e933a01a999f7094ff7c23fbd2beeaa67c", size = 49749, upload-time = "2025-10-06T05:37:10.569Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/fabede9fafd976b991e9f1b9c8c873ed86f202889b864756f240ce6dd855/frozenlist-1.8.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:cba69cb73723c3f329622e34bdbf5ce1f80c21c290ff04256cff1cd3c2036ed2", size = 231298, upload-time = "2025-10-06T05:37:11.993Z" }, + { url = "https://files.pythonhosted.org/packages/3a/3b/d9b1e0b0eed36e70477ffb8360c49c85c8ca8ef9700a4e6711f39a6e8b45/frozenlist-1.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:778a11b15673f6f1df23d9586f83c4846c471a8af693a22e066508b77d201ec8", size = 232015, upload-time = "2025-10-06T05:37:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/dc/94/be719d2766c1138148564a3960fc2c06eb688da592bdc25adcf856101be7/frozenlist-1.8.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0325024fe97f94c41c08872db482cf8ac4800d80e79222c6b0b7b162d5b13686", size = 225038, upload-time = "2025-10-06T05:37:14.577Z" }, + { url = "https://files.pythonhosted.org/packages/e4/09/6712b6c5465f083f52f50cf74167b92d4ea2f50e46a9eea0523d658454ae/frozenlist-1.8.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:97260ff46b207a82a7567b581ab4190bd4dfa09f4db8a8b49d1a958f6aa4940e", size = 240130, upload-time = "2025-10-06T05:37:15.781Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d4/cd065cdcf21550b54f3ce6a22e143ac9e4836ca42a0de1022da8498eac89/frozenlist-1.8.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:54b2077180eb7f83dd52c40b2750d0a9f175e06a42e3213ce047219de902717a", size = 242845, upload-time = "2025-10-06T05:37:17.037Z" }, + { url = "https://files.pythonhosted.org/packages/62/c3/f57a5c8c70cd1ead3d5d5f776f89d33110b1addae0ab010ad774d9a44fb9/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2f05983daecab868a31e1da44462873306d3cbfd76d1f0b5b69c473d21dbb128", size = 229131, upload-time = "2025-10-06T05:37:18.221Z" }, + { url = "https://files.pythonhosted.org/packages/6c/52/232476fe9cb64f0742f3fde2b7d26c1dac18b6d62071c74d4ded55e0ef94/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:33f48f51a446114bc5d251fb2954ab0164d5be02ad3382abcbfe07e2531d650f", size = 240542, upload-time = "2025-10-06T05:37:19.771Z" }, + { url = "https://files.pythonhosted.org/packages/5f/85/07bf3f5d0fb5414aee5f47d33c6f5c77bfe49aac680bfece33d4fdf6a246/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:154e55ec0655291b5dd1b8731c637ecdb50975a2ae70c606d100750a540082f7", size = 237308, upload-time = "2025-10-06T05:37:20.969Z" }, + { url = "https://files.pythonhosted.org/packages/11/99/ae3a33d5befd41ac0ca2cc7fd3aa707c9c324de2e89db0e0f45db9a64c26/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:4314debad13beb564b708b4a496020e5306c7333fa9a3ab90374169a20ffab30", size = 238210, upload-time = "2025-10-06T05:37:22.252Z" }, + { url = "https://files.pythonhosted.org/packages/b2/60/b1d2da22f4970e7a155f0adde9b1435712ece01b3cd45ba63702aea33938/frozenlist-1.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:073f8bf8becba60aa931eb3bc420b217bb7d5b8f4750e6f8b3be7f3da85d38b7", size = 231972, upload-time = "2025-10-06T05:37:23.5Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ab/945b2f32de889993b9c9133216c068b7fcf257d8595a0ac420ac8677cab0/frozenlist-1.8.0-cp314-cp314-win32.whl", hash = "sha256:bac9c42ba2ac65ddc115d930c78d24ab8d4f465fd3fc473cdedfccadb9429806", size = 40536, upload-time = "2025-10-06T05:37:25.581Z" }, + { url = "https://files.pythonhosted.org/packages/59/ad/9caa9b9c836d9ad6f067157a531ac48b7d36499f5036d4141ce78c230b1b/frozenlist-1.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:3e0761f4d1a44f1d1a47996511752cf3dcec5bbdd9cc2b4fe595caf97754b7a0", size = 44330, upload-time = "2025-10-06T05:37:26.928Z" }, + { url = "https://files.pythonhosted.org/packages/82/13/e6950121764f2676f43534c555249f57030150260aee9dcf7d64efda11dd/frozenlist-1.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:d1eaff1d00c7751b7c6662e9c5ba6eb2c17a2306ba5e2a37f24ddf3cc953402b", size = 40627, upload-time = "2025-10-06T05:37:28.075Z" }, + { url = "https://files.pythonhosted.org/packages/c0/c7/43200656ecc4e02d3f8bc248df68256cd9572b3f0017f0a0c4e93440ae23/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d3bb933317c52d7ea5004a1c442eef86f426886fba134ef8cf4226ea6ee1821d", size = 89238, upload-time = "2025-10-06T05:37:29.373Z" }, + { url = "https://files.pythonhosted.org/packages/d1/29/55c5f0689b9c0fb765055629f472c0de484dcaf0acee2f7707266ae3583c/frozenlist-1.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:8009897cdef112072f93a0efdce29cd819e717fd2f649ee3016efd3cd885a7ed", size = 50738, upload-time = "2025-10-06T05:37:30.792Z" }, + { url = "https://files.pythonhosted.org/packages/ba/7d/b7282a445956506fa11da8c2db7d276adcbf2b17d8bb8407a47685263f90/frozenlist-1.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2c5dcbbc55383e5883246d11fd179782a9d07a986c40f49abe89ddf865913930", size = 51739, upload-time = "2025-10-06T05:37:32.127Z" }, + { url = "https://files.pythonhosted.org/packages/62/1c/3d8622e60d0b767a5510d1d3cf21065b9db874696a51ea6d7a43180a259c/frozenlist-1.8.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:39ecbc32f1390387d2aa4f5a995e465e9e2f79ba3adcac92d68e3e0afae6657c", size = 284186, upload-time = "2025-10-06T05:37:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/2d/14/aa36d5f85a89679a85a1d44cd7a6657e0b1c75f61e7cad987b203d2daca8/frozenlist-1.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92db2bf818d5cc8d9c1f1fc56b897662e24ea5adb36ad1f1d82875bd64e03c24", size = 292196, upload-time = "2025-10-06T05:37:36.107Z" }, + { url = "https://files.pythonhosted.org/packages/05/23/6bde59eb55abd407d34f77d39a5126fb7b4f109a3f611d3929f14b700c66/frozenlist-1.8.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2dc43a022e555de94c3b68a4ef0b11c4f747d12c024a520c7101709a2144fb37", size = 273830, upload-time = "2025-10-06T05:37:37.663Z" }, + { url = "https://files.pythonhosted.org/packages/d2/3f/22cff331bfad7a8afa616289000ba793347fcd7bc275f3b28ecea2a27909/frozenlist-1.8.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb89a7f2de3602cfed448095bab3f178399646ab7c61454315089787df07733a", size = 294289, upload-time = "2025-10-06T05:37:39.261Z" }, + { url = "https://files.pythonhosted.org/packages/a4/89/5b057c799de4838b6c69aa82b79705f2027615e01be996d2486a69ca99c4/frozenlist-1.8.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:33139dc858c580ea50e7e60a1b0ea003efa1fd42e6ec7fdbad78fff65fad2fd2", size = 300318, upload-time = "2025-10-06T05:37:43.213Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/2c22ab3eb2a8af6d69dc799e48455813bab3690c760de58e1bf43b36da3e/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:168c0969a329b416119507ba30b9ea13688fafffac1b7822802537569a1cb0ef", size = 282814, upload-time = "2025-10-06T05:37:45.337Z" }, + { url = "https://files.pythonhosted.org/packages/59/f7/970141a6a8dbd7f556d94977858cfb36fa9b66e0892c6dd780d2219d8cd8/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:28bd570e8e189d7f7b001966435f9dac6718324b5be2990ac496cf1ea9ddb7fe", size = 291762, upload-time = "2025-10-06T05:37:46.657Z" }, + { url = "https://files.pythonhosted.org/packages/c1/15/ca1adae83a719f82df9116d66f5bb28bb95557b3951903d39135620ef157/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:b2a095d45c5d46e5e79ba1e5b9cb787f541a8dee0433836cea4b96a2c439dcd8", size = 289470, upload-time = "2025-10-06T05:37:47.946Z" }, + { url = "https://files.pythonhosted.org/packages/ac/83/dca6dc53bf657d371fbc88ddeb21b79891e747189c5de990b9dfff2ccba1/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:eab8145831a0d56ec9c4139b6c3e594c7a83c2c8be25d5bcf2d86136a532287a", size = 289042, upload-time = "2025-10-06T05:37:49.499Z" }, + { url = "https://files.pythonhosted.org/packages/96/52/abddd34ca99be142f354398700536c5bd315880ed0a213812bc491cff5e4/frozenlist-1.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:974b28cf63cc99dfb2188d8d222bc6843656188164848c4f679e63dae4b0708e", size = 283148, upload-time = "2025-10-06T05:37:50.745Z" }, + { url = "https://files.pythonhosted.org/packages/af/d3/76bd4ed4317e7119c2b7f57c3f6934aba26d277acc6309f873341640e21f/frozenlist-1.8.0-cp314-cp314t-win32.whl", hash = "sha256:342c97bf697ac5480c0a7ec73cd700ecfa5a8a40ac923bd035484616efecc2df", size = 44676, upload-time = "2025-10-06T05:37:52.222Z" }, + { url = "https://files.pythonhosted.org/packages/89/76/c615883b7b521ead2944bb3480398cbb07e12b7b4e4d073d3752eb721558/frozenlist-1.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:06be8f67f39c8b1dc671f5d83aaefd3358ae5cdcf8314552c57e7ed3e6475bdd", size = 49451, upload-time = "2025-10-06T05:37:53.425Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a3/5982da14e113d07b325230f95060e2169f5311b1017ea8af2a29b374c289/frozenlist-1.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:102e6314ca4da683dca92e3b1355490fed5f313b768500084fbe6371fddfdb79", size = 42507, upload-time = "2025-10-06T05:37:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, +] + +[[package]] +name = "fsspec" +version = "2025.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, +] + +[[package]] +name = "google-ai-generativelanguage" +version = "0.6.18" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/77/3e89a4c4200135eac74eca2f6c9153127e3719a825681ad55f5a4a58b422/google_ai_generativelanguage-0.6.18.tar.gz", hash = "sha256:274ba9fcf69466ff64e971d565884434388e523300afd468fc8e3033cd8e606e", size = 1444757, upload-time = "2025-04-29T15:45:45.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/77/ca2889903a2d93b3072a49056d48b3f55410219743e338a1d7f94dc6455e/google_ai_generativelanguage-0.6.18-py3-none-any.whl", hash = "sha256:13d8174fea90b633f520789d32df7b422058fd5883b022989c349f1017db7fcf", size = 1372256, upload-time = "2025-04-29T15:45:43.601Z" }, +] + +[[package]] +name = "google-api-core" +version = "2.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/ea/e7b6ac3c7b557b728c2d0181010548cbbdd338e9002513420c5a354fa8df/google_api_core-2.26.0.tar.gz", hash = "sha256:e6e6d78bd6cf757f4aee41dcc85b07f485fbb069d5daa3afb126defba1e91a62", size = 166369, upload-time = "2025-10-08T21:37:38.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/ad/f73cf9fe9bd95918502b270e3ddb8764e4c900b3bbd7782b90c56fac14bb/google_api_core-2.26.0-py3-none-any.whl", hash = "sha256:2b204bd0da2c81f918e3582c48458e24c11771f987f6258e6e227212af78f3ed", size = 162505, upload-time = "2025-10-08T21:37:36.651Z" }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.41.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/af/5129ce5b2f9688d2fa49b463e544972a7c82b0fdb50980dafee92e121d9f/google_auth-2.41.1.tar.gz", hash = "sha256:b76b7b1f9e61f0cb7e88870d14f6a94aeef248959ef6992670efee37709cbfd2", size = 292284, upload-time = "2025-09-30T22:51:26.363Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/a4/7319a2a8add4cc352be9e3efeff5e2aacee917c85ca2fa1647e29089983c/google_auth-2.41.1-py2.py3-none-any.whl", hash = "sha256:754843be95575b9a19c604a848a41be03f7f2afd8c019f716dc1f51ee41c639d", size = 221302, upload-time = "2025-09-30T22:51:24.212Z" }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.71.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/43/b25abe02db2911397819003029bef768f68a974f2ece483e6084d1a5f754/googleapis_common_protos-1.71.0.tar.gz", hash = "sha256:1aec01e574e29da63c80ba9f7bbf1ccfaacf1da877f23609fe236ca7c72a2e2e", size = 146454, upload-time = "2025-10-20T14:58:08.732Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/e8/eba9fece11d57a71e3e22ea672742c8f3cf23b35730c9e96db768b295216/googleapis_common_protos-1.71.0-py3-none-any.whl", hash = "sha256:59034a1d849dc4d18971997a72ac56246570afdd17f9369a0ff68218d50ab78c", size = 294576, upload-time = "2025-10-20T14:56:21.295Z" }, +] + +[[package]] +name = "gradio" +version = "5.49.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiofiles" }, + { name = "anyio" }, + { name = "audioop-lts", marker = "python_full_version >= '3.13'" }, + { name = "brotli" }, + { name = "fastapi" }, + { name = "ffmpy" }, + { name = "gradio-client" }, + { name = "groovy" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "jinja2" }, + { name = "markupsafe" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "pydantic" }, + { name = "pydub" }, + { name = "python-multipart" }, + { name = "pyyaml" }, + { name = "ruff" }, + { name = "safehttpx" }, + { name = "semantic-version" }, + { name = "starlette" }, + { name = "tomlkit" }, + { name = "typer" }, + { name = "typing-extensions" }, + { name = "uvicorn" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/67/17b3969a686f204dfb8f06bd34d1423bcba1df8a2f3674f115ca427188b7/gradio-5.49.1.tar.gz", hash = "sha256:c06faa324ae06c3892c8b4b4e73c706c4520d380f6b9e52a3c02dc53a7627ba9", size = 73784504, upload-time = "2025-10-08T20:18:40.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/95/1c25fbcabfa201ab79b016c8716a4ac0f846121d4bbfd2136ffb6d87f31e/gradio-5.49.1-py3-none-any.whl", hash = "sha256:1b19369387801a26a6ba7fd2f74d46c5b0e2ac9ddef14f24ddc0d11fb19421b7", size = 63523840, upload-time = "2025-10-08T20:18:34.585Z" }, +] + +[[package]] +name = "gradio-client" +version = "1.13.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fsspec" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "packaging" }, + { name = "typing-extensions" }, + { name = "websockets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/a9/a3beb0ece8c05c33e6376b790fa42e0dd157abca8220cf639b249a597467/gradio_client-1.13.3.tar.gz", hash = "sha256:869b3e67e0f7a0f40df8c48c94de99183265cf4b7b1d9bd4623e336d219ffbe7", size = 323253, upload-time = "2025-09-26T19:51:21.7Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/0b/337b74504681b5dde39f20d803bb09757f9973ecdc65fd4e819d4b11faf7/gradio_client-1.13.3-py3-none-any.whl", hash = "sha256:3f63e4d33a2899c1a12b10fe3cf77b82a6919ff1a1fb6391f6aa225811aa390c", size = 325350, upload-time = "2025-09-26T19:51:20.288Z" }, +] + +[[package]] +name = "greenlet" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/03/b8/704d753a5a45507a7aab61f18db9509302ed3d0a27ac7e0359ec2905b1a6/greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", size = 188260, upload-time = "2025-08-07T13:24:33.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/de/f28ced0a67749cac23fecb02b694f6473f47686dff6afaa211d186e2ef9c/greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", size = 272305, upload-time = "2025-08-07T13:15:41.288Z" }, + { url = "https://files.pythonhosted.org/packages/09/16/2c3792cba130000bf2a31c5272999113f4764fd9d874fb257ff588ac779a/greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", size = 632472, upload-time = "2025-08-07T13:42:55.044Z" }, + { url = "https://files.pythonhosted.org/packages/ae/8f/95d48d7e3d433e6dae5b1682e4292242a53f22df82e6d3dda81b1701a960/greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", size = 644646, upload-time = "2025-08-07T13:45:26.523Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5e/405965351aef8c76b8ef7ad370e5da58d57ef6068df197548b015464001a/greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", size = 640519, upload-time = "2025-08-07T13:53:13.928Z" }, + { url = "https://files.pythonhosted.org/packages/25/5d/382753b52006ce0218297ec1b628e048c4e64b155379331f25a7316eb749/greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", size = 639707, upload-time = "2025-08-07T13:18:27.146Z" }, + { url = "https://files.pythonhosted.org/packages/1f/8e/abdd3f14d735b2929290a018ecf133c901be4874b858dd1c604b9319f064/greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", size = 587684, upload-time = "2025-08-07T13:18:25.164Z" }, + { url = "https://files.pythonhosted.org/packages/5d/65/deb2a69c3e5996439b0176f6651e0052542bb6c8f8ec2e3fba97c9768805/greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", size = 1116647, upload-time = "2025-08-07T13:42:38.655Z" }, + { url = "https://files.pythonhosted.org/packages/3f/cc/b07000438a29ac5cfb2194bfc128151d52f333cee74dd7dfe3fb733fc16c/greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", size = 1142073, upload-time = "2025-08-07T13:18:21.737Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0f/30aef242fcab550b0b3520b8e3561156857c94288f0332a79928c31a52cf/greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", size = 299100, upload-time = "2025-08-07T13:44:12.287Z" }, + { url = "https://files.pythonhosted.org/packages/44/69/9b804adb5fd0671f367781560eb5eb586c4d495277c93bde4307b9e28068/greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", size = 274079, upload-time = "2025-08-07T13:15:45.033Z" }, + { url = "https://files.pythonhosted.org/packages/46/e9/d2a80c99f19a153eff70bc451ab78615583b8dac0754cfb942223d2c1a0d/greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", size = 640997, upload-time = "2025-08-07T13:42:56.234Z" }, + { url = "https://files.pythonhosted.org/packages/3b/16/035dcfcc48715ccd345f3a93183267167cdd162ad123cd93067d86f27ce4/greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", size = 655185, upload-time = "2025-08-07T13:45:27.624Z" }, + { url = "https://files.pythonhosted.org/packages/31/da/0386695eef69ffae1ad726881571dfe28b41970173947e7c558d9998de0f/greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", size = 649926, upload-time = "2025-08-07T13:53:15.251Z" }, + { url = "https://files.pythonhosted.org/packages/68/88/69bf19fd4dc19981928ceacbc5fd4bb6bc2215d53199e367832e98d1d8fe/greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", size = 651839, upload-time = "2025-08-07T13:18:30.281Z" }, + { url = "https://files.pythonhosted.org/packages/19/0d/6660d55f7373b2ff8152401a83e02084956da23ae58cddbfb0b330978fe9/greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", size = 607586, upload-time = "2025-08-07T13:18:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1a/c953fdedd22d81ee4629afbb38d2f9d71e37d23caace44775a3a969147d4/greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", size = 1123281, upload-time = "2025-08-07T13:42:39.858Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c7/12381b18e21aef2c6bd3a636da1088b888b97b7a0362fac2e4de92405f97/greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", size = 1151142, upload-time = "2025-08-07T13:18:22.981Z" }, + { url = "https://files.pythonhosted.org/packages/e9/08/b0814846b79399e585f974bbeebf5580fbe59e258ea7be64d9dfb253c84f/greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", size = 299899, upload-time = "2025-08-07T13:38:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/49/e8/58c7f85958bda41dafea50497cbd59738c5c43dbbea5ee83d651234398f4/greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", size = 272814, upload-time = "2025-08-07T13:15:50.011Z" }, + { url = "https://files.pythonhosted.org/packages/62/dd/b9f59862e9e257a16e4e610480cfffd29e3fae018a68c2332090b53aac3d/greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", size = 641073, upload-time = "2025-08-07T13:42:57.23Z" }, + { url = "https://files.pythonhosted.org/packages/f7/0b/bc13f787394920b23073ca3b6c4a7a21396301ed75a655bcb47196b50e6e/greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", size = 655191, upload-time = "2025-08-07T13:45:29.752Z" }, + { url = "https://files.pythonhosted.org/packages/f2/d6/6adde57d1345a8d0f14d31e4ab9c23cfe8e2cd39c3baf7674b4b0338d266/greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", size = 649516, upload-time = "2025-08-07T13:53:16.314Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3b/3a3328a788d4a473889a2d403199932be55b1b0060f4ddd96ee7cdfcad10/greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", size = 652169, upload-time = "2025-08-07T13:18:32.861Z" }, + { url = "https://files.pythonhosted.org/packages/ee/43/3cecdc0349359e1a527cbf2e3e28e5f8f06d3343aaf82ca13437a9aa290f/greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", size = 610497, upload-time = "2025-08-07T13:18:31.636Z" }, + { url = "https://files.pythonhosted.org/packages/b8/19/06b6cf5d604e2c382a6f31cafafd6f33d5dea706f4db7bdab184bad2b21d/greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", size = 1121662, upload-time = "2025-08-07T13:42:41.117Z" }, + { url = "https://files.pythonhosted.org/packages/a2/15/0d5e4e1a66fab130d98168fe984c509249c833c1a3c16806b90f253ce7b9/greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", size = 1149210, upload-time = "2025-08-07T13:18:24.072Z" }, + { url = "https://files.pythonhosted.org/packages/0b/55/2321e43595e6801e105fcfdee02b34c0f996eb71e6ddffca6b10b7e1d771/greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", size = 299685, upload-time = "2025-08-07T13:24:38.824Z" }, + { url = "https://files.pythonhosted.org/packages/22/5c/85273fd7cc388285632b0498dbbab97596e04b154933dfe0f3e68156c68c/greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", size = 273586, upload-time = "2025-08-07T13:16:08.004Z" }, + { url = "https://files.pythonhosted.org/packages/d1/75/10aeeaa3da9332c2e761e4c50d4c3556c21113ee3f0afa2cf5769946f7a3/greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", size = 686346, upload-time = "2025-08-07T13:42:59.944Z" }, + { url = "https://files.pythonhosted.org/packages/c0/aa/687d6b12ffb505a4447567d1f3abea23bd20e73a5bed63871178e0831b7a/greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", size = 699218, upload-time = "2025-08-07T13:45:30.969Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8b/29aae55436521f1d6f8ff4e12fb676f3400de7fcf27fccd1d4d17fd8fecd/greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", size = 694659, upload-time = "2025-08-07T13:53:17.759Z" }, + { url = "https://files.pythonhosted.org/packages/92/2e/ea25914b1ebfde93b6fc4ff46d6864564fba59024e928bdc7de475affc25/greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", size = 695355, upload-time = "2025-08-07T13:18:34.517Z" }, + { url = "https://files.pythonhosted.org/packages/72/60/fc56c62046ec17f6b0d3060564562c64c862948c9d4bc8aa807cf5bd74f4/greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", size = 657512, upload-time = "2025-08-07T13:18:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/e3/a5/6ddab2b4c112be95601c13428db1d8b6608a8b6039816f2ba09c346c08fc/greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", size = 303425, upload-time = "2025-08-07T13:32:27.59Z" }, +] + +[[package]] +name = "groovy" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/36/bbdede67400277bef33d3ec0e6a31750da972c469f75966b4930c753218f/groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083", size = 17325, upload-time = "2025-02-28T20:24:56.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/27/3d6dcadc8a3214d8522c1e7f6a19554e33659be44546d44a2f7572ac7d2a/groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64", size = 14090, upload-time = "2025-02-28T20:24:55.152Z" }, +] + +[[package]] +name = "grpcio" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/e0/318c1ce3ae5a17894d5791e87aea147587c9e702f24122cc7a5c8bbaeeb1/grpcio-1.76.0.tar.gz", hash = "sha256:7be78388d6da1a25c0d5ec506523db58b18be22d9c37d8d3a32c08be4987bd73", size = 12785182, upload-time = "2025-10-21T16:23:12.106Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/00/8163a1beeb6971f66b4bbe6ac9457b97948beba8dd2fc8e1281dce7f79ec/grpcio-1.76.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:2e1743fbd7f5fa713a1b0a8ac8ebabf0ec980b5d8809ec358d488e273b9cf02a", size = 5843567, upload-time = "2025-10-21T16:20:52.829Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/934202f5cf335e6d852530ce14ddb0fef21be612ba9ecbbcbd4d748ca32d/grpcio-1.76.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:a8c2cf1209497cf659a667d7dea88985e834c24b7c3b605e6254cbb5076d985c", size = 11848017, upload-time = "2025-10-21T16:20:56.705Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/8dec16b1863d74af6eb3543928600ec2195af49ca58b16334972f6775663/grpcio-1.76.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:08caea849a9d3c71a542827d6df9d5a69067b0a1efbea8a855633ff5d9571465", size = 6412027, upload-time = "2025-10-21T16:20:59.3Z" }, + { url = "https://files.pythonhosted.org/packages/d7/64/7b9e6e7ab910bea9d46f2c090380bab274a0b91fb0a2fe9b0cd399fffa12/grpcio-1.76.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:f0e34c2079d47ae9f6188211db9e777c619a21d4faba6977774e8fa43b085e48", size = 7075913, upload-time = "2025-10-21T16:21:01.645Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/093c46e9546073cefa789bd76d44c5cb2abc824ca62af0c18be590ff13ba/grpcio-1.76.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8843114c0cfce61b40ad48df65abcfc00d4dba82eae8718fab5352390848c5da", size = 6615417, upload-time = "2025-10-21T16:21:03.844Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b6/5709a3a68500a9c03da6fb71740dcdd5ef245e39266461a03f31a57036d8/grpcio-1.76.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8eddfb4d203a237da6f3cc8a540dad0517d274b5a1e9e636fd8d2c79b5c1d397", size = 7199683, upload-time = "2025-10-21T16:21:06.195Z" }, + { url = "https://files.pythonhosted.org/packages/91/d3/4b1f2bf16ed52ce0b508161df3a2d186e4935379a159a834cb4a7d687429/grpcio-1.76.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:32483fe2aab2c3794101c2a159070584e5db11d0aa091b2c0ea9c4fc43d0d749", size = 8163109, upload-time = "2025-10-21T16:21:08.498Z" }, + { url = "https://files.pythonhosted.org/packages/5c/61/d9043f95f5f4cf085ac5dd6137b469d41befb04bd80280952ffa2a4c3f12/grpcio-1.76.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dcfe41187da8992c5f40aa8c5ec086fa3672834d2be57a32384c08d5a05b4c00", size = 7626676, upload-time = "2025-10-21T16:21:10.693Z" }, + { url = "https://files.pythonhosted.org/packages/36/95/fd9a5152ca02d8881e4dd419cdd790e11805979f499a2e5b96488b85cf27/grpcio-1.76.0-cp311-cp311-win32.whl", hash = "sha256:2107b0c024d1b35f4083f11245c0e23846ae64d02f40b2b226684840260ed054", size = 3997688, upload-time = "2025-10-21T16:21:12.746Z" }, + { url = "https://files.pythonhosted.org/packages/60/9c/5c359c8d4c9176cfa3c61ecd4efe5affe1f38d9bae81e81ac7186b4c9cc8/grpcio-1.76.0-cp311-cp311-win_amd64.whl", hash = "sha256:522175aba7af9113c48ec10cc471b9b9bd4f6ceb36aeb4544a8e2c80ed9d252d", size = 4709315, upload-time = "2025-10-21T16:21:15.26Z" }, + { url = "https://files.pythonhosted.org/packages/bf/05/8e29121994b8d959ffa0afd28996d452f291b48cfc0875619de0bde2c50c/grpcio-1.76.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:81fd9652b37b36f16138611c7e884eb82e0cec137c40d3ef7c3f9b3ed00f6ed8", size = 5799718, upload-time = "2025-10-21T16:21:17.939Z" }, + { url = "https://files.pythonhosted.org/packages/d9/75/11d0e66b3cdf998c996489581bdad8900db79ebd83513e45c19548f1cba4/grpcio-1.76.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:04bbe1bfe3a68bbfd4e52402ab7d4eb59d72d02647ae2042204326cf4bbad280", size = 11825627, upload-time = "2025-10-21T16:21:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/2f0aa0498bc188048f5d9504dcc5c2c24f2eb1a9337cd0fa09a61a2e75f0/grpcio-1.76.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d388087771c837cdb6515539f43b9d4bf0b0f23593a24054ac16f7a960be16f4", size = 6359167, upload-time = "2025-10-21T16:21:23.122Z" }, + { url = "https://files.pythonhosted.org/packages/66/e5/bbf0bb97d29ede1d59d6588af40018cfc345b17ce979b7b45424628dc8bb/grpcio-1.76.0-cp312-cp312-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:9f8f757bebaaea112c00dba718fc0d3260052ce714e25804a03f93f5d1c6cc11", size = 7044267, upload-time = "2025-10-21T16:21:25.995Z" }, + { url = "https://files.pythonhosted.org/packages/f5/86/f6ec2164f743d9609691115ae8ece098c76b894ebe4f7c94a655c6b03e98/grpcio-1.76.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:980a846182ce88c4f2f7e2c22c56aefd515daeb36149d1c897f83cf57999e0b6", size = 6573963, upload-time = "2025-10-21T16:21:28.631Z" }, + { url = "https://files.pythonhosted.org/packages/60/bc/8d9d0d8505feccfdf38a766d262c71e73639c165b311c9457208b56d92ae/grpcio-1.76.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f92f88e6c033db65a5ae3d97905c8fea9c725b63e28d5a75cb73b49bda5024d8", size = 7164484, upload-time = "2025-10-21T16:21:30.837Z" }, + { url = "https://files.pythonhosted.org/packages/67/e6/5d6c2fc10b95edf6df9b8f19cf10a34263b7fd48493936fffd5085521292/grpcio-1.76.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4baf3cbe2f0be3289eb68ac8ae771156971848bb8aaff60bad42005539431980", size = 8127777, upload-time = "2025-10-21T16:21:33.577Z" }, + { url = "https://files.pythonhosted.org/packages/3f/c8/dce8ff21c86abe025efe304d9e31fdb0deaaa3b502b6a78141080f206da0/grpcio-1.76.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:615ba64c208aaceb5ec83bfdce7728b80bfeb8be97562944836a7a0a9647d882", size = 7594014, upload-time = "2025-10-21T16:21:41.882Z" }, + { url = "https://files.pythonhosted.org/packages/e0/42/ad28191ebf983a5d0ecef90bab66baa5a6b18f2bfdef9d0a63b1973d9f75/grpcio-1.76.0-cp312-cp312-win32.whl", hash = "sha256:45d59a649a82df5718fd9527ce775fd66d1af35e6d31abdcdc906a49c6822958", size = 3984750, upload-time = "2025-10-21T16:21:44.006Z" }, + { url = "https://files.pythonhosted.org/packages/9e/00/7bd478cbb851c04a48baccaa49b75abaa8e4122f7d86da797500cccdd771/grpcio-1.76.0-cp312-cp312-win_amd64.whl", hash = "sha256:c088e7a90b6017307f423efbb9d1ba97a22aa2170876223f9709e9d1de0b5347", size = 4704003, upload-time = "2025-10-21T16:21:46.244Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ed/71467ab770effc9e8cef5f2e7388beb2be26ed642d567697bb103a790c72/grpcio-1.76.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:26ef06c73eb53267c2b319f43e6634c7556ea37672029241a056629af27c10e2", size = 5807716, upload-time = "2025-10-21T16:21:48.475Z" }, + { url = "https://files.pythonhosted.org/packages/2c/85/c6ed56f9817fab03fa8a111ca91469941fb514e3e3ce6d793cb8f1e1347b/grpcio-1.76.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:45e0111e73f43f735d70786557dc38141185072d7ff8dc1829d6a77ac1471468", size = 11821522, upload-time = "2025-10-21T16:21:51.142Z" }, + { url = "https://files.pythonhosted.org/packages/ac/31/2b8a235ab40c39cbc141ef647f8a6eb7b0028f023015a4842933bc0d6831/grpcio-1.76.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:83d57312a58dcfe2a3a0f9d1389b299438909a02db60e2f2ea2ae2d8034909d3", size = 6362558, upload-time = "2025-10-21T16:21:54.213Z" }, + { url = "https://files.pythonhosted.org/packages/bd/64/9784eab483358e08847498ee56faf8ff6ea8e0a4592568d9f68edc97e9e9/grpcio-1.76.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3e2a27c89eb9ac3d81ec8835e12414d73536c6e620355d65102503064a4ed6eb", size = 7049990, upload-time = "2025-10-21T16:21:56.476Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/8c12319a6369434e7a184b987e8e9f3b49a114c489b8315f029e24de4837/grpcio-1.76.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:61f69297cba3950a524f61c7c8ee12e55c486cb5f7db47ff9dcee33da6f0d3ae", size = 6575387, upload-time = "2025-10-21T16:21:59.051Z" }, + { url = "https://files.pythonhosted.org/packages/15/0f/f12c32b03f731f4a6242f771f63039df182c8b8e2cf8075b245b409259d4/grpcio-1.76.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6a15c17af8839b6801d554263c546c69c4d7718ad4321e3166175b37eaacca77", size = 7166668, upload-time = "2025-10-21T16:22:02.049Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/3ec9ce0c2b1d92dd59d1c3264aaec9f0f7c817d6e8ac683b97198a36ed5a/grpcio-1.76.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:25a18e9810fbc7e7f03ec2516addc116a957f8cbb8cbc95ccc80faa072743d03", size = 8124928, upload-time = "2025-10-21T16:22:04.984Z" }, + { url = "https://files.pythonhosted.org/packages/1a/74/fd3317be5672f4856bcdd1a9e7b5e17554692d3db9a3b273879dc02d657d/grpcio-1.76.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:931091142fd8cc14edccc0845a79248bc155425eee9a98b2db2ea4f00a235a42", size = 7589983, upload-time = "2025-10-21T16:22:07.881Z" }, + { url = "https://files.pythonhosted.org/packages/45/bb/ca038cf420f405971f19821c8c15bcbc875505f6ffadafe9ffd77871dc4c/grpcio-1.76.0-cp313-cp313-win32.whl", hash = "sha256:5e8571632780e08526f118f74170ad8d50fb0a48c23a746bef2a6ebade3abd6f", size = 3984727, upload-time = "2025-10-21T16:22:10.032Z" }, + { url = "https://files.pythonhosted.org/packages/41/80/84087dc56437ced7cdd4b13d7875e7439a52a261e3ab4e06488ba6173b0a/grpcio-1.76.0-cp313-cp313-win_amd64.whl", hash = "sha256:f9f7bd5faab55f47231ad8dba7787866b69f5e93bc306e3915606779bbfb4ba8", size = 4702799, upload-time = "2025-10-21T16:22:12.709Z" }, + { url = "https://files.pythonhosted.org/packages/b4/46/39adac80de49d678e6e073b70204091e76631e03e94928b9ea4ecf0f6e0e/grpcio-1.76.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:ff8a59ea85a1f2191a0ffcc61298c571bc566332f82e5f5be1b83c9d8e668a62", size = 5808417, upload-time = "2025-10-21T16:22:15.02Z" }, + { url = "https://files.pythonhosted.org/packages/9c/f5/a4531f7fb8b4e2a60b94e39d5d924469b7a6988176b3422487be61fe2998/grpcio-1.76.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:06c3d6b076e7b593905d04fdba6a0525711b3466f43b3400266f04ff735de0cd", size = 11828219, upload-time = "2025-10-21T16:22:17.954Z" }, + { url = "https://files.pythonhosted.org/packages/4b/1c/de55d868ed7a8bd6acc6b1d6ddc4aa36d07a9f31d33c912c804adb1b971b/grpcio-1.76.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fd5ef5932f6475c436c4a55e4336ebbe47bd3272be04964a03d316bbf4afbcbc", size = 6367826, upload-time = "2025-10-21T16:22:20.721Z" }, + { url = "https://files.pythonhosted.org/packages/59/64/99e44c02b5adb0ad13ab3adc89cb33cb54bfa90c74770f2607eea629b86f/grpcio-1.76.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b331680e46239e090f5b3cead313cc772f6caa7d0fc8de349337563125361a4a", size = 7049550, upload-time = "2025-10-21T16:22:23.637Z" }, + { url = "https://files.pythonhosted.org/packages/43/28/40a5be3f9a86949b83e7d6a2ad6011d993cbe9b6bd27bea881f61c7788b6/grpcio-1.76.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2229ae655ec4e8999599469559e97630185fdd53ae1e8997d147b7c9b2b72cba", size = 6575564, upload-time = "2025-10-21T16:22:26.016Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a9/1be18e6055b64467440208a8559afac243c66a8b904213af6f392dc2212f/grpcio-1.76.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:490fa6d203992c47c7b9e4a9d39003a0c2bcc1c9aa3c058730884bbbb0ee9f09", size = 7176236, upload-time = "2025-10-21T16:22:28.362Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/dba05d3fcc151ce6e81327541d2cc8394f442f6b350fead67401661bf041/grpcio-1.76.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:479496325ce554792dba6548fae3df31a72cef7bad71ca2e12b0e58f9b336bfc", size = 8125795, upload-time = "2025-10-21T16:22:31.075Z" }, + { url = "https://files.pythonhosted.org/packages/4a/45/122df922d05655f63930cf42c9e3f72ba20aadb26c100ee105cad4ce4257/grpcio-1.76.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1c9b93f79f48b03ada57ea24725d83a30284a012ec27eab2cf7e50a550cbbbcc", size = 7592214, upload-time = "2025-10-21T16:22:33.831Z" }, + { url = "https://files.pythonhosted.org/packages/4a/6e/0b899b7f6b66e5af39e377055fb4a6675c9ee28431df5708139df2e93233/grpcio-1.76.0-cp314-cp314-win32.whl", hash = "sha256:747fa73efa9b8b1488a95d0ba1039c8e2dca0f741612d80415b1e1c560febf4e", size = 4062961, upload-time = "2025-10-21T16:22:36.468Z" }, + { url = "https://files.pythonhosted.org/packages/19/41/0b430b01a2eb38ee887f88c1f07644a1df8e289353b78e82b37ef988fb64/grpcio-1.76.0-cp314-cp314-win_amd64.whl", hash = "sha256:922fa70ba549fce362d2e2871ab542082d66e2aaf0c19480ea453905b01f384e", size = 4834462, upload-time = "2025-10-21T16:22:39.772Z" }, +] + +[[package]] +name = "grpcio-status" +version = "1.76.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/46/e9f19d5be65e8423f886813a2a9d0056ba94757b0c5007aa59aed1a961fa/grpcio_status-1.76.0.tar.gz", hash = "sha256:25fcbfec74c15d1a1cb5da3fab8ee9672852dc16a5a9eeb5baf7d7a9952943cd", size = 13679, upload-time = "2025-10-21T16:28:52.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/cc/27ba60ad5a5f2067963e6a858743500df408eb5855e98be778eaef8c9b02/grpcio_status-1.76.0-py3-none-any.whl", hash = "sha256:380568794055a8efbbd8871162df92012e0228a5f6dffaf57f2a00c534103b18", size = 14425, upload-time = "2025-10-21T16:28:40.853Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "h2" +version = "4.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "hpack" }, + { name = "hyperframe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/17/afa56379f94ad0fe8defd37d6eb3f89a25404ffc71d4d848893d270325fc/h2-4.3.0.tar.gz", hash = "sha256:6c59efe4323fa18b47a632221a1888bd7fde6249819beda254aeca909f221bf1", size = 2152026, upload-time = "2025-08-23T18:12:19.778Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/b2/119f6e6dcbd96f9069ce9a2665e0146588dc9f88f29549711853645e736a/h2-4.3.0-py3-none-any.whl", hash = "sha256:c438f029a25f7945c69e0ccf0fb951dc3f73a5f6412981daee861431b70e2bdd", size = 61779, upload-time = "2025-08-23T18:12:17.779Z" }, +] + +[[package]] +name = "hf-xet" +version = "1.1.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/31/feeddfce1748c4a233ec1aa5b7396161c07ae1aa9b7bdbc9a72c3c7dd768/hf_xet-1.1.10.tar.gz", hash = "sha256:408aef343800a2102374a883f283ff29068055c111f003ff840733d3b715bb97", size = 487910, upload-time = "2025-09-12T20:10:27.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/a2/343e6d05de96908366bdc0081f2d8607d61200be2ac802769c4284cc65bd/hf_xet-1.1.10-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:686083aca1a6669bc85c21c0563551cbcdaa5cf7876a91f3d074a030b577231d", size = 2761466, upload-time = "2025-09-12T20:10:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/31/f9/6215f948ac8f17566ee27af6430ea72045e0418ce757260248b483f4183b/hf_xet-1.1.10-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:71081925383b66b24eedff3013f8e6bbd41215c3338be4b94ba75fd75b21513b", size = 2623807, upload-time = "2025-09-12T20:10:21.118Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/86397573efefff941e100367bbda0b21496ffcdb34db7ab51912994c32a2/hf_xet-1.1.10-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b6bceb6361c80c1cc42b5a7b4e3efd90e64630bcf11224dcac50ef30a47e435", size = 3186960, upload-time = "2025-09-12T20:10:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/01/a7/0b2e242b918cc30e1f91980f3c4b026ff2eedaf1e2ad96933bca164b2869/hf_xet-1.1.10-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eae7c1fc8a664e54753ffc235e11427ca61f4b0477d757cc4eb9ae374b69f09c", size = 3087167, upload-time = "2025-09-12T20:10:17.255Z" }, + { url = "https://files.pythonhosted.org/packages/4a/25/3e32ab61cc7145b11eee9d745988e2f0f4fafda81b25980eebf97d8cff15/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0a0005fd08f002180f7a12d4e13b22be277725bc23ed0529f8add5c7a6309c06", size = 3248612, upload-time = "2025-09-12T20:10:24.093Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3d/ab7109e607ed321afaa690f557a9ada6d6d164ec852fd6bf9979665dc3d6/hf_xet-1.1.10-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f900481cf6e362a6c549c61ff77468bd59d6dd082f3170a36acfef2eb6a6793f", size = 3353360, upload-time = "2025-09-12T20:10:25.563Z" }, + { url = "https://files.pythonhosted.org/packages/ee/0e/471f0a21db36e71a2f1752767ad77e92d8cde24e974e03d662931b1305ec/hf_xet-1.1.10-cp37-abi3-win_amd64.whl", hash = "sha256:5f54b19cc347c13235ae7ee98b330c26dd65ef1df47e5316ffb1e87713ca7045", size = 2804691, upload-time = "2025-09-12T20:10:28.433Z" }, +] + +[[package]] +name = "hpack" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/48/71de9ed269fdae9c8057e5a4c0aa7402e8bb16f2c6e90b3aa53327b113f8/hpack-4.1.0.tar.gz", hash = "sha256:ec5eca154f7056aa06f196a557655c5b009b382873ac8d1e66e79e87535f1dca", size = 51276, upload-time = "2025-01-22T21:44:58.347Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/c6/80c95b1b2b94682a72cbdbfb85b81ae2daffa4291fbfa1b1464502ede10d/hpack-4.1.0-py3-none-any.whl", hash = "sha256:157ac792668d995c657d93111f46b4535ed114f0c9c8d672271bbec7eae1b496", size = 34357, upload-time = "2025-01-22T21:44:56.92Z" }, +] + +[[package]] +name = "html2text" +version = "2025.4.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/27/e158d86ba1e82967cc2f790b0cb02030d4a8bef58e0c79a8590e9678107f/html2text-2025.4.15.tar.gz", hash = "sha256:948a645f8f0bc3abe7fd587019a2197a12436cd73d0d4908af95bfc8da337588", size = 64316, upload-time = "2025-04-15T04:02:30.045Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/84/1a0f9555fd5f2b1c924ff932d99b40a0f8a6b12f6dd625e2a47f415b00ea/html2text-2025.4.15-py3-none-any.whl", hash = "sha256:00569167ffdab3d7767a4cdf589b7f57e777a5ed28d12907d8c58769ec734acc", size = 34656, upload-time = "2025-04-15T04:02:28.44Z" }, +] + +[[package]] +name = "htmldate" +version = "1.9.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "charset-normalizer" }, + { name = "dateparser" }, + { name = "lxml" }, + { name = "python-dateutil" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/26/aaae4cab984f0b7dd0f5f1b823fa2ed2fd4a2bb50acd5bd2f0d217562678/htmldate-1.9.3.tar.gz", hash = "sha256:ac0caf4628c3ded4042011e2d60dc68dfb314c77b106587dd307a80d77e708e9", size = 44913, upload-time = "2024-12-30T12:52:35.206Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/49/8872130016209c20436ce0c1067de1cf630755d0443d068a5bc17fa95015/htmldate-1.9.3-py3-none-any.whl", hash = "sha256:3fadc422cf3c10a5cdb5e1b914daf37ec7270400a80a1b37e2673ff84faaaff8", size = 31565, upload-time = "2024-12-30T12:52:32.145Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[package.optional-dependencies] +http2 = [ + { name = "h2" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "huggingface-hub" +version = "0.35.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "fsspec" }, + { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, + { name = "packaging" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/7e/a0a97de7c73671863ca6b3f61fa12518caf35db37825e43d63a70956738c/huggingface_hub-0.35.3.tar.gz", hash = "sha256:350932eaa5cc6a4747efae85126ee220e4ef1b54e29d31c3b45c5612ddf0b32a", size = 461798, upload-time = "2025-09-29T14:29:58.625Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/a0/651f93d154cb72323358bf2bbae3e642bdb5d2f1bfc874d096f7cb159fa0/huggingface_hub-0.35.3-py3-none-any.whl", hash = "sha256:0e3a01829c19d86d03793e4577816fe3bdfc1602ac62c7fb220d593d351224ba", size = 564262, upload-time = "2025-09-29T14:29:55.813Z" }, +] + +[[package]] +name = "hyperframe" +version = "6.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/e7/94f8232d4a74cc99514c13a9f995811485a6903d48e5d952771ef6322e30/hyperframe-6.1.0.tar.gz", hash = "sha256:f630908a00854a7adeabd6382b43923a4c4cd4b821fcb527e6ab9e15382a3b08", size = 26566, upload-time = "2025-01-22T21:41:49.302Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/30/47d0bf6072f7252e6521f3447ccfa40b421b6824517f82854703d0f5a98b/hyperframe-6.1.0-py3-none-any.whl", hash = "sha256:b03380493a519fce58ea5af42e4a42317bf9bd425596f7a0835ffce80f1a42e5", size = 13007, upload-time = "2025-01-22T21:41:47.295Z" }, +] + +[[package]] +name = "ibm-cos-sdk" +version = "2.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ibm-cos-sdk-core" }, + { name = "ibm-cos-sdk-s3transfer" }, + { name = "jmespath" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/b8/b99f17ece72d4bccd7e75539b9a294d0f73ace5c6c475d8f2631afd6f65b/ibm_cos_sdk-2.14.3.tar.gz", hash = "sha256:643b6f2aa1683adad7f432df23407d11ae5adb9d9ad01214115bee77dc64364a", size = 58831, upload-time = "2025-08-01T06:35:51.722Z" } + +[[package]] +name = "ibm-cos-sdk-core" +version = "2.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jmespath" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/45/80c23aa1e13175a9deefe43cbf8e853a3d3bfc8dfa8b6d6fe83e5785fe21/ibm_cos_sdk_core-2.14.3.tar.gz", hash = "sha256:85dee7790c92e8db69bf39dae4c02cac211e3c1d81bb86e64fa2d1e929674623", size = 1103637, upload-time = "2025-08-01T06:35:41.645Z" } + +[[package]] +name = "ibm-cos-sdk-s3transfer" +version = "2.14.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ibm-cos-sdk-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/ff/c9baf0997266d398ae08347951a2970e5e96ed6232ed0252f649f2b9a7eb/ibm_cos_sdk_s3transfer-2.14.3.tar.gz", hash = "sha256:2251ebfc4a46144401e431f4a5d9f04c262a0d6f95c88a8e71071da056e55f72", size = 139594, upload-time = "2025-08-01T06:35:46.403Z" } + +[[package]] +name = "ibm-watsonx-ai" +version = "1.4.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "certifi" }, + { name = "httpx" }, + { name = "ibm-cos-sdk" }, + { name = "lomond" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "requests" }, + { name = "tabulate" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/1a/c587f82831a18a363d997c452572600098873ada17f46a0627ec98adc0f3/ibm_watsonx_ai-1.4.1.tar.gz", hash = "sha256:58f0e4ce994f52020cc436b26859fe83b92efd4257830c2b924e13990b134297", size = 690598, upload-time = "2025-10-15T12:33:59.162Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/ea/c93a544ec683e03c1bd1e5b6c2061a9ffc42f0117121228585d8571d843b/ibm_watsonx_ai-1.4.1-py3-none-any.whl", hash = "sha256:23baca05fd9099b47d62eea587d9d2d343b6e13b4594399804ac3370aaa2bd1b", size = 1060075, upload-time = "2025-10-15T12:33:57.672Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/68/0357982493a7b20925aece061f7fb7a2678e3b232f8d73a6edb7e5304443/jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc", size = 168385, upload-time = "2025-10-17T11:31:15.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/34/c9e6cfe876f9a24f43ed53fe29f052ce02bd8d5f5a387dbf46ad3764bef0/jiter-0.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b0088ff3c374ce8ce0168523ec8e97122ebb788f950cf7bb8e39c7dc6a876a2", size = 310160, upload-time = "2025-10-17T11:28:59.174Z" }, + { url = "https://files.pythonhosted.org/packages/bc/9f/b06ec8181d7165858faf2ac5287c54fe52b2287760b7fe1ba9c06890255f/jiter-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74433962dd3c3090655e02e461267095d6c84f0741c7827de11022ef8d7ff661", size = 316573, upload-time = "2025-10-17T11:29:00.905Z" }, + { url = "https://files.pythonhosted.org/packages/66/49/3179d93090f2ed0c6b091a9c210f266d2d020d82c96f753260af536371d0/jiter-0.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d98030e345e6546df2cc2c08309c502466c66c4747b043f1a0d415fada862b8", size = 348998, upload-time = "2025-10-17T11:29:02.321Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/63db2c8eabda7a9cad65a2e808ca34aaa8689d98d498f5a2357d7a2e2cec/jiter-0.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d6db0b2e788db46bec2cf729a88b6dd36959af2abd9fa2312dfba5acdd96dcb", size = 363413, upload-time = "2025-10-17T11:29:03.787Z" }, + { url = "https://files.pythonhosted.org/packages/25/ff/3e6b3170c5053053c7baddb8d44e2bf11ff44cd71024a280a8438ae6ba32/jiter-0.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55678fbbda261eafe7289165dd2ddd0e922df5f9a1ae46d7c79a5a15242bd7d1", size = 487144, upload-time = "2025-10-17T11:29:05.37Z" }, + { url = "https://files.pythonhosted.org/packages/b0/50/b63fcadf699893269b997f4c2e88400bc68f085c6db698c6e5e69d63b2c1/jiter-0.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6b74fae8e40497653b52ce6ca0f1b13457af769af6fb9c1113efc8b5b4d9be", size = 376215, upload-time = "2025-10-17T11:29:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/39/8c/57a8a89401134167e87e73471b9cca321cf651c1fd78c45f3a0f16932213/jiter-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a55a453f8b035eb4f7852a79a065d616b7971a17f5e37a9296b4b38d3b619e4", size = 359163, upload-time = "2025-10-17T11:29:09.047Z" }, + { url = "https://files.pythonhosted.org/packages/4b/96/30b0cdbffbb6f753e25339d3dbbe26890c9ef119928314578201c758aace/jiter-0.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2638148099022e6bdb3f42904289cd2e403609356fb06eb36ddec2d50958bc29", size = 385344, upload-time = "2025-10-17T11:29:10.69Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d5/31dae27c1cc9410ad52bb514f11bfa4f286f7d6ef9d287b98b8831e156ec/jiter-0.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:252490567a5d990986f83b95a5f1ca1bf205ebd27b3e9e93bb7c2592380e29b9", size = 517972, upload-time = "2025-10-17T11:29:12.174Z" }, + { url = "https://files.pythonhosted.org/packages/61/1e/5905a7a3aceab80de13ab226fd690471a5e1ee7e554dc1015e55f1a6b896/jiter-0.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d431d52b0ca2436eea6195f0f48528202100c7deda354cb7aac0a302167594d5", size = 508408, upload-time = "2025-10-17T11:29:13.597Z" }, + { url = "https://files.pythonhosted.org/packages/91/12/1c49b97aa49077e136e8591cef7162f0d3e2860ae457a2d35868fd1521ef/jiter-0.11.1-cp311-cp311-win32.whl", hash = "sha256:db6f41e40f8bae20c86cb574b48c4fd9f28ee1c71cb044e9ec12e78ab757ba3a", size = 203937, upload-time = "2025-10-17T11:29:14.894Z" }, + { url = "https://files.pythonhosted.org/packages/6d/9d/2255f7c17134ee9892c7e013c32d5bcf4bce64eb115402c9fe5e727a67eb/jiter-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0cc407b8e6cdff01b06bb80f61225c8b090c3df108ebade5e0c3c10993735b19", size = 207589, upload-time = "2025-10-17T11:29:16.166Z" }, + { url = "https://files.pythonhosted.org/packages/3c/28/6307fc8f95afef84cae6caf5429fee58ef16a582c2ff4db317ceb3e352fa/jiter-0.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:fe04ea475392a91896d1936367854d346724a1045a247e5d1c196410473b8869", size = 188391, upload-time = "2025-10-17T11:29:17.488Z" }, + { url = "https://files.pythonhosted.org/packages/15/8b/318e8af2c904a9d29af91f78c1e18f0592e189bbdb8a462902d31fe20682/jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c", size = 305655, upload-time = "2025-10-17T11:29:18.859Z" }, + { url = "https://files.pythonhosted.org/packages/f7/29/6c7de6b5d6e511d9e736312c0c9bfcee8f9b6bef68182a08b1d78767e627/jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d", size = 315645, upload-time = "2025-10-17T11:29:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5f/ef9e5675511ee0eb7f98dd8c90509e1f7743dbb7c350071acae87b0145f3/jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b", size = 348003, upload-time = "2025-10-17T11:29:22.712Z" }, + { url = "https://files.pythonhosted.org/packages/56/1b/abe8c4021010b0a320d3c62682769b700fb66f92c6db02d1a1381b3db025/jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4", size = 365122, upload-time = "2025-10-17T11:29:24.408Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2d/4a18013939a4f24432f805fbd5a19893e64650b933edb057cd405275a538/jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239", size = 488360, upload-time = "2025-10-17T11:29:25.724Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/38124f5d02ac4131f0dfbcfd1a19a0fac305fa2c005bc4f9f0736914a1a4/jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711", size = 376884, upload-time = "2025-10-17T11:29:27.056Z" }, + { url = "https://files.pythonhosted.org/packages/7b/43/59fdc2f6267959b71dd23ce0bd8d4aeaf55566aa435a5d00f53d53c7eb24/jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939", size = 358827, upload-time = "2025-10-17T11:29:28.698Z" }, + { url = "https://files.pythonhosted.org/packages/7d/d0/b3cc20ff5340775ea3bbaa0d665518eddecd4266ba7244c9cb480c0c82ec/jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54", size = 385171, upload-time = "2025-10-17T11:29:30.078Z" }, + { url = "https://files.pythonhosted.org/packages/d2/bc/94dd1f3a61f4dc236f787a097360ec061ceeebebf4ea120b924d91391b10/jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d", size = 518359, upload-time = "2025-10-17T11:29:31.464Z" }, + { url = "https://files.pythonhosted.org/packages/7e/8c/12ee132bd67e25c75f542c227f5762491b9a316b0dad8e929c95076f773c/jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250", size = 509205, upload-time = "2025-10-17T11:29:32.895Z" }, + { url = "https://files.pythonhosted.org/packages/39/d5/9de848928ce341d463c7e7273fce90ea6d0ea4343cd761f451860fa16b59/jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e", size = 205448, upload-time = "2025-10-17T11:29:34.217Z" }, + { url = "https://files.pythonhosted.org/packages/ee/b0/8002d78637e05009f5e3fb5288f9d57d65715c33b5d6aa20fd57670feef5/jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87", size = 204285, upload-time = "2025-10-17T11:29:35.446Z" }, + { url = "https://files.pythonhosted.org/packages/9f/a2/bb24d5587e4dff17ff796716542f663deee337358006a80c8af43ddc11e5/jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c", size = 188712, upload-time = "2025-10-17T11:29:37.027Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4b/e4dd3c76424fad02a601d570f4f2a8438daea47ba081201a721a903d3f4c/jiter-0.11.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:71b6a920a5550f057d49d0e8bcc60945a8da998019e83f01adf110e226267663", size = 305272, upload-time = "2025-10-17T11:29:39.249Z" }, + { url = "https://files.pythonhosted.org/packages/67/83/2cd3ad5364191130f4de80eacc907f693723beaab11a46c7d155b07a092c/jiter-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b3de72e925388453a5171be83379549300db01284f04d2a6f244d1d8de36f94", size = 314038, upload-time = "2025-10-17T11:29:40.563Z" }, + { url = "https://files.pythonhosted.org/packages/d3/3c/8e67d9ba524e97d2f04c8f406f8769a23205026b13b0938d16646d6e2d3e/jiter-0.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc19dd65a2bd3d9c044c5b4ebf657ca1e6003a97c0fc10f555aa4f7fb9821c00", size = 345977, upload-time = "2025-10-17T11:29:42.009Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/489ce64d992c29bccbffabb13961bbb0435e890d7f2d266d1f3df5e917d2/jiter-0.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d58faaa936743cd1464540562f60b7ce4fd927e695e8bc31b3da5b914baa9abd", size = 364503, upload-time = "2025-10-17T11:29:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/d4/c0/e321dd83ee231d05c8fe4b1a12caf1f0e8c7a949bf4724d58397104f10f2/jiter-0.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:902640c3103625317291cb73773413b4d71847cdf9383ba65528745ff89f1d14", size = 487092, upload-time = "2025-10-17T11:29:44.835Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5e/8f24ec49c8d37bd37f34ec0112e0b1a3b4b5a7b456c8efff1df5e189ad43/jiter-0.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30405f726e4c2ed487b176c09f8b877a957f535d60c1bf194abb8dadedb5836f", size = 376328, upload-time = "2025-10-17T11:29:46.175Z" }, + { url = "https://files.pythonhosted.org/packages/7f/70/ded107620e809327cf7050727e17ccfa79d6385a771b7fe38fb31318ef00/jiter-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3217f61728b0baadd2551844870f65219ac4a1285d5e1a4abddff3d51fdabe96", size = 356632, upload-time = "2025-10-17T11:29:47.454Z" }, + { url = "https://files.pythonhosted.org/packages/19/53/c26f7251613f6a9079275ee43c89b8a973a95ff27532c421abc2a87afb04/jiter-0.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1364cc90c03a8196f35f396f84029f12abe925415049204446db86598c8b72c", size = 384358, upload-time = "2025-10-17T11:29:49.377Z" }, + { url = "https://files.pythonhosted.org/packages/84/16/e0f2cc61e9c4d0b62f6c1bd9b9781d878a427656f88293e2a5335fa8ff07/jiter-0.11.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53a54bf8e873820ab186b2dca9f6c3303f00d65ae5e7b7d6bda1b95aa472d646", size = 517279, upload-time = "2025-10-17T11:29:50.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/5c/4cd095eaee68961bca3081acbe7c89e12ae24a5dae5fd5d2a13e01ed2542/jiter-0.11.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7e29aca023627b0e0c2392d4248f6414d566ff3974fa08ff2ac8dbb96dfee92a", size = 508276, upload-time = "2025-10-17T11:29:52.619Z" }, + { url = "https://files.pythonhosted.org/packages/4f/25/f459240e69b0e09a7706d96ce203ad615ca36b0fe832308d2b7123abf2d0/jiter-0.11.1-cp313-cp313-win32.whl", hash = "sha256:f153e31d8bca11363751e875c0a70b3d25160ecbaee7b51e457f14498fb39d8b", size = 205593, upload-time = "2025-10-17T11:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/7c/16/461bafe22bae79bab74e217a09c907481a46d520c36b7b9fe71ee8c9e983/jiter-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:f773f84080b667c69c4ea0403fc67bb08b07e2b7ce1ef335dea5868451e60fed", size = 203518, upload-time = "2025-10-17T11:29:55.216Z" }, + { url = "https://files.pythonhosted.org/packages/7b/72/c45de6e320edb4fa165b7b1a414193b3cae302dd82da2169d315dcc78b44/jiter-0.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:635ecd45c04e4c340d2187bcb1cea204c7cc9d32c1364d251564bf42e0e39c2d", size = 188062, upload-time = "2025-10-17T11:29:56.631Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/4a57922437ca8753ef823f434c2dec5028b237d84fa320f06a3ba1aec6e8/jiter-0.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d892b184da4d94d94ddb4031296931c74ec8b325513a541ebfd6dfb9ae89904b", size = 313814, upload-time = "2025-10-17T11:29:58.509Z" }, + { url = "https://files.pythonhosted.org/packages/76/50/62a0683dadca25490a4bedc6a88d59de9af2a3406dd5a576009a73a1d392/jiter-0.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa22c223a3041dacb2fcd37c70dfd648b44662b4a48e242592f95bda5ab09d58", size = 344987, upload-time = "2025-10-17T11:30:00.208Z" }, + { url = "https://files.pythonhosted.org/packages/da/00/2355dbfcbf6cdeaddfdca18287f0f38ae49446bb6378e4a5971e9356fc8a/jiter-0.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330e8e6a11ad4980cd66a0f4a3e0e2e0f646c911ce047014f984841924729789", size = 356399, upload-time = "2025-10-17T11:30:02.084Z" }, + { url = "https://files.pythonhosted.org/packages/c9/07/c2bd748d578fa933d894a55bff33f983bc27f75fc4e491b354bef7b78012/jiter-0.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09e2e386ebf298547ca3a3704b729471f7ec666c2906c5c26c1a915ea24741ec", size = 203289, upload-time = "2025-10-17T11:30:03.656Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ee/ace64a853a1acbd318eb0ca167bad1cf5ee037207504b83a868a5849747b/jiter-0.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:fe4a431c291157e11cee7c34627990ea75e8d153894365a3bc84b7a959d23ca8", size = 188284, upload-time = "2025-10-17T11:30:05.046Z" }, + { url = "https://files.pythonhosted.org/packages/8d/00/d6006d069e7b076e4c66af90656b63da9481954f290d5eca8c715f4bf125/jiter-0.11.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0fa1f70da7a8a9713ff8e5f75ec3f90c0c870be6d526aa95e7c906f6a1c8c676", size = 304624, upload-time = "2025-10-17T11:30:06.678Z" }, + { url = "https://files.pythonhosted.org/packages/fc/45/4a0e31eb996b9ccfddbae4d3017b46f358a599ccf2e19fbffa5e531bd304/jiter-0.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:569ee559e5046a42feb6828c55307cf20fe43308e3ae0d8e9e4f8d8634d99944", size = 315042, upload-time = "2025-10-17T11:30:08.87Z" }, + { url = "https://files.pythonhosted.org/packages/e7/91/22f5746f5159a28c76acdc0778801f3c1181799aab196dbea2d29e064968/jiter-0.11.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69955fa1d92e81987f092b233f0be49d4c937da107b7f7dcf56306f1d3fcce9", size = 346357, upload-time = "2025-10-17T11:30:10.222Z" }, + { url = "https://files.pythonhosted.org/packages/f5/4f/57620857d4e1dc75c8ff4856c90cb6c135e61bff9b4ebfb5dc86814e82d7/jiter-0.11.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:090f4c9d4a825e0fcbd0a2647c9a88a0f366b75654d982d95a9590745ff0c48d", size = 365057, upload-time = "2025-10-17T11:30:11.585Z" }, + { url = "https://files.pythonhosted.org/packages/ce/34/caf7f9cc8ae0a5bb25a5440cc76c7452d264d1b36701b90fdadd28fe08ec/jiter-0.11.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf3d8cedf9e9d825233e0dcac28ff15c47b7c5512fdfe2e25fd5bbb6e6b0cee", size = 487086, upload-time = "2025-10-17T11:30:13.052Z" }, + { url = "https://files.pythonhosted.org/packages/50/17/85b5857c329d533d433fedf98804ebec696004a1f88cabad202b2ddc55cf/jiter-0.11.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa9b1958f9c30d3d1a558b75f0626733c60eb9b7774a86b34d88060be1e67fe", size = 376083, upload-time = "2025-10-17T11:30:14.416Z" }, + { url = "https://files.pythonhosted.org/packages/85/d3/2d9f973f828226e6faebdef034097a2918077ea776fb4d88489949024787/jiter-0.11.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42d1ca16590b768c5e7d723055acd2633908baacb3628dd430842e2e035aa90", size = 357825, upload-time = "2025-10-17T11:30:15.765Z" }, + { url = "https://files.pythonhosted.org/packages/f4/55/848d4dabf2c2c236a05468c315c2cb9dc736c5915e65449ccecdba22fb6f/jiter-0.11.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5db4c2486a023820b701a17aec9c5a6173c5ba4393f26662f032f2de9c848b0f", size = 383933, upload-time = "2025-10-17T11:30:17.34Z" }, + { url = "https://files.pythonhosted.org/packages/0b/6c/204c95a4fbb0e26dfa7776c8ef4a878d0c0b215868011cc904bf44f707e2/jiter-0.11.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4573b78777ccfac954859a6eff45cbd9d281d80c8af049d0f1a3d9fc323d5c3a", size = 517118, upload-time = "2025-10-17T11:30:18.684Z" }, + { url = "https://files.pythonhosted.org/packages/88/25/09956644ea5a2b1e7a2a0f665cb69a973b28f4621fa61fc0c0f06ff40a31/jiter-0.11.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7593ac6f40831d7961cb67633c39b9fef6689a211d7919e958f45710504f52d3", size = 508194, upload-time = "2025-10-17T11:30:20.719Z" }, + { url = "https://files.pythonhosted.org/packages/09/49/4d1657355d7f5c9e783083a03a3f07d5858efa6916a7d9634d07db1c23bd/jiter-0.11.1-cp314-cp314-win32.whl", hash = "sha256:87202ec6ff9626ff5f9351507def98fcf0df60e9a146308e8ab221432228f4ea", size = 203961, upload-time = "2025-10-17T11:30:22.073Z" }, + { url = "https://files.pythonhosted.org/packages/76/bd/f063bd5cc2712e7ca3cf6beda50894418fc0cfeb3f6ff45a12d87af25996/jiter-0.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:a5dd268f6531a182c89d0dd9a3f8848e86e92dfff4201b77a18e6b98aa59798c", size = 202804, upload-time = "2025-10-17T11:30:23.452Z" }, + { url = "https://files.pythonhosted.org/packages/52/ca/4d84193dfafef1020bf0bedd5e1a8d0e89cb67c54b8519040effc694964b/jiter-0.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:5d761f863f912a44748a21b5c4979c04252588ded8d1d2760976d2e42cd8d991", size = 188001, upload-time = "2025-10-17T11:30:24.915Z" }, + { url = "https://files.pythonhosted.org/packages/d5/fa/3b05e5c9d32efc770a8510eeb0b071c42ae93a5b576fd91cee9af91689a1/jiter-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2cc5a3965285ddc33e0cab933e96b640bc9ba5940cea27ebbbf6695e72d6511c", size = 312561, upload-time = "2025-10-17T11:30:26.742Z" }, + { url = "https://files.pythonhosted.org/packages/50/d3/335822eb216154ddb79a130cbdce88fdf5c3e2b43dc5dba1fd95c485aaf5/jiter-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b572b3636a784c2768b2342f36a23078c8d3aa6d8a30745398b1bab58a6f1a8", size = 344551, upload-time = "2025-10-17T11:30:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/31/6d/a0bed13676b1398f9b3ba61f32569f20a3ff270291161100956a577b2dd3/jiter-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad93e3d67a981f96596d65d2298fe8d1aa649deb5374a2fb6a434410ee11915e", size = 363051, upload-time = "2025-10-17T11:30:30.009Z" }, + { url = "https://files.pythonhosted.org/packages/a4/03/313eda04aa08545a5a04ed5876e52f49ab76a4d98e54578896ca3e16313e/jiter-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83097ce379e202dcc3fe3fc71a16d523d1ee9192c8e4e854158f96b3efe3f2f", size = 485897, upload-time = "2025-10-17T11:30:31.429Z" }, + { url = "https://files.pythonhosted.org/packages/5f/13/a1011b9d325e40b53b1b96a17c010b8646013417f3902f97a86325b19299/jiter-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7042c51e7fbeca65631eb0c332f90c0c082eab04334e7ccc28a8588e8e2804d9", size = 375224, upload-time = "2025-10-17T11:30:33.18Z" }, + { url = "https://files.pythonhosted.org/packages/92/da/1b45026b19dd39b419e917165ff0ea629dbb95f374a3a13d2df95e40a6ac/jiter-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a68d679c0e47649a61df591660507608adc2652442de7ec8276538ac46abe08", size = 356606, upload-time = "2025-10-17T11:30:34.572Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9acb0e54d6a8ba59ce923a180ebe824b4e00e80e56cefde86cc8e0a948be/jiter-0.11.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b0da75dbf4b6ec0b3c9e604d1ee8beaf15bc046fff7180f7d89e3cdbd3bb51", size = 384003, upload-time = "2025-10-17T11:30:35.987Z" }, + { url = "https://files.pythonhosted.org/packages/3f/2b/e5a5fe09d6da2145e4eed651e2ce37f3c0cf8016e48b1d302e21fb1628b7/jiter-0.11.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:69dd514bf0fa31c62147d6002e5ca2b3e7ef5894f5ac6f0a19752385f4e89437", size = 516946, upload-time = "2025-10-17T11:30:37.425Z" }, + { url = "https://files.pythonhosted.org/packages/5f/fe/db936e16e0228d48eb81f9934e8327e9fde5185e84f02174fcd22a01be87/jiter-0.11.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:bb31ac0b339efa24c0ca606febd8b77ef11c58d09af1b5f2be4c99e907b11111", size = 507614, upload-time = "2025-10-17T11:30:38.977Z" }, + { url = "https://files.pythonhosted.org/packages/86/db/c4438e8febfb303486d13c6b72f5eb71cf851e300a0c1f0b4140018dd31f/jiter-0.11.1-cp314-cp314t-win32.whl", hash = "sha256:b2ce0d6156a1d3ad41da3eec63b17e03e296b78b0e0da660876fccfada86d2f7", size = 204043, upload-time = "2025-10-17T11:30:40.308Z" }, + { url = "https://files.pythonhosted.org/packages/36/59/81badb169212f30f47f817dfaabf965bc9b8204fed906fab58104ee541f9/jiter-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f4db07d127b54c4a2d43b4cf05ff0193e4f73e0dd90c74037e16df0b29f666e1", size = 204046, upload-time = "2025-10-17T11:30:41.692Z" }, + { url = "https://files.pythonhosted.org/packages/dd/01/43f7b4eb61db3e565574c4c5714685d042fb652f9eef7e5a3de6aafa943a/jiter-0.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:28e4fdf2d7ebfc935523e50d1efa3970043cfaa161674fe66f9642409d001dfe", size = 188069, upload-time = "2025-10-17T11:30:43.23Z" }, + { url = "https://files.pythonhosted.org/packages/9d/51/bd41562dd284e2a18b6dc0a99d195fd4a3560d52ab192c42e56fe0316643/jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:e642b5270e61dd02265866398707f90e365b5db2eb65a4f30c789d826682e1f6", size = 306871, upload-time = "2025-10-17T11:31:03.616Z" }, + { url = "https://files.pythonhosted.org/packages/ba/cb/64e7f21dd357e8cd6b3c919c26fac7fc198385bbd1d85bb3b5355600d787/jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:464ba6d000585e4e2fd1e891f31f1231f497273414f5019e27c00a4b8f7a24ad", size = 301454, upload-time = "2025-10-17T11:31:05.338Z" }, + { url = "https://files.pythonhosted.org/packages/55/b0/54bdc00da4ef39801b1419a01035bd8857983de984fd3776b0be6b94add7/jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055568693ab35e0bf3a171b03bb40b2dcb10352359e0ab9b5ed0da2bf1eb6f6f", size = 336801, upload-time = "2025-10-17T11:31:06.893Z" }, + { url = "https://files.pythonhosted.org/packages/de/8f/87176ed071d42e9db415ed8be787ef4ef31a4fa27f52e6a4fbf34387bd28/jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c69ea798d08a915ba4478113efa9e694971e410056392f4526d796f136d3fa", size = 343452, upload-time = "2025-10-17T11:31:08.259Z" }, + { url = "https://files.pythonhosted.org/packages/a6/bc/950dd7f170c6394b6fdd73f989d9e729bd98907bcc4430ef080a72d06b77/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d", size = 302626, upload-time = "2025-10-17T11:31:09.645Z" }, + { url = "https://files.pythonhosted.org/packages/3a/65/43d7971ca82ee100b7b9b520573eeef7eabc0a45d490168ebb9a9b5bb8b2/jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838", size = 297034, upload-time = "2025-10-17T11:31:10.975Z" }, + { url = "https://files.pythonhosted.org/packages/19/4c/000e1e0c0c67e96557a279f8969487ea2732d6c7311698819f977abae837/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f", size = 337328, upload-time = "2025-10-17T11:31:12.399Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/71408b02c6133153336d29fa3ba53000f1e1a3f78bb2fc2d1a1865d2e743/jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960", size = 343697, upload-time = "2025-10-17T11:31:13.773Z" }, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/2a/e867e8531cf3e36b41201936b7fa7ba7b5702dbef42922193f05c8976cd6/jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe", size = 25843, upload-time = "2022-06-17T18:00:12.224Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b4/b9b800c45527aadd64d5b442f9b932b00648617eb5d63d2c7a6587b7cafc/jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980", size = 20256, upload-time = "2022-06-17T18:00:10.251Z" }, +] + +[[package]] +name = "json-repair" +version = "0.52.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/93/5220c447b9ce20ed14ab33bae9a29772be895a8949bb723eaa30cc42a4e1/json_repair-0.52.2.tar.gz", hash = "sha256:1c83e1811d7e57092ad531b333f083166bdf398b042c95f3cd62b30d74dc7ecd", size = 35584, upload-time = "2025-10-20T07:24:20.221Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/20/1935a6082988efea16432cecfdb757111122c32a07acaa595ccd78a55c47/json_repair-0.52.2-py3-none-any.whl", hash = "sha256:c7bb514d3f59d49364653717233eb4466bda0f4fdd511b4dc268aa877d406c81", size = 26512, upload-time = "2025-10-20T07:24:18.893Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.25.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "justext" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml", extra = ["html-clean"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/f3/45890c1b314f0d04e19c1c83d534e611513150939a7cf039664d9ab1e649/justext-3.0.2.tar.gz", hash = "sha256:13496a450c44c4cd5b5a75a5efcd9996066d2a189794ea99a49949685a0beb05", size = 828521, upload-time = "2025-02-25T20:21:49.934Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/ac/52f4e86d1924a7fc05af3aeb34488570eccc39b4af90530dd6acecdf16b5/justext-3.0.2-py2.py3-none-any.whl", hash = "sha256:62b1c562b15c3c6265e121cc070874243a443bfd53060e869393f09d6b6cc9a7", size = 837940, upload-time = "2025-02-25T20:21:44.179Z" }, +] + +[[package]] +name = "langchain" +version = "0.3.22" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langchain-text-splitters" }, + { name = "langsmith" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/66/36ccbd6285b29473ada883b0e06fdc0973ca181431d6a0175e473160fbfb/langchain-0.3.22.tar.gz", hash = "sha256:fd7781ef02cac6f074f9c6a902236482c61976e21da96ab577874d4e5396eeda", size = 10225573, upload-time = "2025-03-31T12:38:08.521Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/0e/032de736a8f9b5b5fcfec77bd92831f9f2c8a8b5072289dd1e5cc95e6edc/langchain-0.3.22-py3-none-any.whl", hash = "sha256:2e7f71a1b0280eb70af9c332c7580f6162a97fb9d5e3e87e9d579ad167f50129", size = 1011714, upload-time = "2025-03-31T12:38:05.982Z" }, +] + +[[package]] +name = "langchain-anthropic" +version = "0.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anthropic" }, + { name = "defusedxml" }, + { name = "langchain-core" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5f/ad/f9f77948deeca2c33a55f262ca78cee7c2c3dfbaef849704991517443bf6/langchain_anthropic-0.3.3.tar.gz", hash = "sha256:1faf0aa0aed392a18ed34d00e816d7c748ef342523deacc131690aae08ab4f1b", size = 21003, upload-time = "2025-01-17T20:32:56.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/cf/466b38e46e7071e7367c452bd29d1b4de03e4023685b0c45fc2df728b616/langchain_anthropic-0.3.3-py3-none-any.whl", hash = "sha256:385e6d6d719514369f38304ed5e9b74827feca36f3391595695dcb82696ed04a", size = 22471, upload-time = "2025-01-17T20:32:54.052Z" }, +] + +[[package]] +name = "langchain-aws" +version = "0.2.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "boto3" }, + { name = "langchain-core" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/13/90/455226b38c48a012941d9cd9710f93a03c0a7a29a30b980443b3d54fbba3/langchain_aws-0.2.19.tar.gz", hash = "sha256:041a1f133220baa54b0c39f68c894aa450e4cb1d33c896bb18633b99ddcf1456", size = 96917, upload-time = "2025-04-10T17:44:00.624Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/ce/a8f3cf8fa510cd6a7bffd091aa5a5968f9eeb4b7a5e84657c73ff55c67b5/langchain_aws-0.2.19-py3-none-any.whl", hash = "sha256:967be6127897be77b2337d376724968cd3c8c834981607e9ab2f90d4199f7941", size = 118893, upload-time = "2025-04-10T17:43:59.229Z" }, +] + +[[package]] +name = "langchain-community" +version = "0.3.20" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "dataclasses-json" }, + { name = "httpx-sse" }, + { name = "langchain" }, + { name = "langchain-core" }, + { name = "langsmith" }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "pydantic-settings" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlalchemy" }, + { name = "tenacity" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/86/bb/a07609679781199738934226bb2764c12541573bc4feeaf21e9f3ad5caf4/langchain_community-0.3.20.tar.gz", hash = "sha256:bd83b4f2f818338423439aff3b5be362e1d686342ffada0478cd34c6f5ef5969", size = 33221203, upload-time = "2025-03-18T22:07:34.81Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/4b/2652cfd2baa482cb3cdbec1ccccae1674418b7576f21ba7724d8730de9db/langchain_community-0.3.20-py3-none-any.whl", hash = "sha256:ea3dbf37fbc21020eca8850627546f3c95a8770afc06c4142b40b9ba86b970f7", size = 2524455, upload-time = "2025-03-18T22:07:32.064Z" }, +] + +[[package]] +name = "langchain-core" +version = "0.3.49" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/bd/db939ba59f28a4ac73fa64281e21f5011ce61fd694c03b88946a554d8442/langchain_core-0.3.49.tar.gz", hash = "sha256:d9dbff9bac0021463a986355c13864d6a68c41f8559dbbd399a68e1ebd9b04b9", size = 536469, upload-time = "2025-03-26T18:42:00.598Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/35/27164f5f23517be8639b518130e6235293dae52c41988790e0b50dd7ba11/langchain_core-0.3.49-py3-none-any.whl", hash = "sha256:893ee42c9af13bf2a2d8c2ec15ba00a5c73cccde21a2bd005234ee0e78a2bdf8", size = 420102, upload-time = "2025-03-26T18:41:58.854Z" }, +] + +[[package]] +name = "langchain-deepseek" +version = "0.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langchain-openai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/7f/be5bcf99b3814214a02ac205bda66d49d55a7d5440d47223105cef5df063/langchain_deepseek-0.1.3.tar.gz", hash = "sha256:89dd6aa120fb50dcfcd3d593626d34c1c40deefe4510710d0807fcc19481adf5", size = 7860, upload-time = "2025-03-21T17:11:58.356Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/7d/51b60aa91fa77742fc461704e5a8497e856156ae878102e6942799a78915/langchain_deepseek-0.1.3-py3-none-any.whl", hash = "sha256:8588e826371b417fca65c02f4273b4061eb9815a7bfcd5eb05acaa40d603aa89", size = 7123, upload-time = "2025-03-21T17:11:57.481Z" }, +] + +[[package]] +name = "langchain-google-genai" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filetype" }, + { name = "google-ai-generativelanguage" }, + { name = "langchain-core" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/32/aeaa30a23f495417d71a7b8d9f6a71a40500b9994424c57e89418d96fc52/langchain_google_genai-2.1.2.tar.gz", hash = "sha256:f605501b498288d32914f6f8c0b7c9cfa67432757f596dcb2dbbd8042e892963", size = 38091, upload-time = "2025-03-27T16:04:22.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/82/2a5d3fe54df23d6471768b9558f9a73e1a712065e6c20a228aa3254092aa/langchain_google_genai-2.1.2-py3-none-any.whl", hash = "sha256:eb9c95d551ecc0216e5baef2f2e6ae1b60897e618f273356d31b680022a1a755", size = 42030, upload-time = "2025-03-27T16:04:21.601Z" }, +] + +[[package]] +name = "langchain-ibm" +version = "0.3.19" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ibm-watsonx-ai" }, + { name = "langchain-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/46/62/507fb317653fcd3cfc352a685baa8ef630e26deb8544827d649edfec8016/langchain_ibm-0.3.19.tar.gz", hash = "sha256:a58a58294ca21f13554d9eeb12fb60965b46d7f1247d4978081587b4ebcba83b", size = 38620, upload-time = "2025-10-15T12:17:49.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/b7/d011ecc79130631e88e35fa37f18eb78f6872d5537b0547e6088010a881c/langchain_ibm-0.3.19-py3-none-any.whl", hash = "sha256:8acaba35c39f7c9748256f632ae2d6d5188e0aa6035d92ab1eef0844f5ac2f10", size = 45997, upload-time = "2025-10-15T12:17:48.688Z" }, +] + +[[package]] +name = "langchain-mcp-adapters" +version = "0.1.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "mcp" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/4e/b84af2e379edfb51db78edcfc6eab7dca798f2ce9d74b73e29f5f207685c/langchain_mcp_adapters-0.1.11.tar.gz", hash = "sha256:a217c49086b162344749f7f99a148fc12482e2da8e0260b2e35fc93afb31b38d", size = 23061, upload-time = "2025-10-03T14:53:13.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/cc/5f9b23cce308b2c30246e31712bf1a53ae49d97bab8b3d9bc9cfe364f82c/langchain_mcp_adapters-0.1.11-py3-none-any.whl", hash = "sha256:7b35921e9487bcb3ea3d94bf10341316ac897e2997e8a16032ae514834a9685d", size = 15751, upload-time = "2025-10-03T14:53:12.358Z" }, +] + +[[package]] +name = "langchain-mistralai" +version = "0.2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "langchain-core" }, + { name = "pydantic" }, + { name = "tokenizers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/04/cd75dd40f55925b5fdcc96b0f9a22cc05e3711c2d270cf8b7948d5f389f0/langchain_mistralai-0.2.10.tar.gz", hash = "sha256:698620c7dee8ae85bf1ca1ed5b544285c0764c453efead9a4ae34ab884704ce1", size = 21560, upload-time = "2025-03-27T16:07:51.872Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/d2/d1238951c6f522b7442558cb860dbde9658b8c5d766c6d5d7f7fde0b7f76/langchain_mistralai-0.2.10-py3-none-any.whl", hash = "sha256:fc3bc813eab034335236a3b01ba189cd00bcf2b7e6ac57628d0409438bd13425", size = 16526, upload-time = "2025-03-27T16:07:50.538Z" }, +] + +[[package]] +name = "langchain-ollama" +version = "0.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ollama" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/36/0ed0173ac8d88a0f6d769fb786a5b736f4b449093b9e47aa787ba0f6b0b4/langchain_ollama-0.3.0.tar.gz", hash = "sha256:4989f79d4b2d0d51f3a95e53b4c368c95c6bb64922a9ea40a7a376b43187803b", size = 20674, upload-time = "2025-03-21T15:53:11.814Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/a1/a7dbdc39365f2f148a91724d8d52c0028cafe7dd6f0257462bc187bc4643/langchain_ollama-0.3.0-py3-none-any.whl", hash = "sha256:33716a912419d00a17da446f1b6ec8ec45c7b9376c6a1c0b688cc0cecd4b9c39", size = 20348, upload-time = "2025-03-21T15:53:10.913Z" }, +] + +[[package]] +name = "langchain-openai" +version = "0.3.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/d6/dc77062c0b7c09f18d10a94a33920a69b6bee13079905d638bfdb7300e97/langchain_openai-0.3.11.tar.gz", hash = "sha256:4de846b2770c2b15bee4ec8034af064bfecb01fa86d4c5ff3f427ee337f0e98c", size = 267476, upload-time = "2025-03-26T19:59:19.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/9f/08696493db3c3fa238c13eee9db6386dbcebe0fc164c8ce6a20afdde53a7/langchain_openai-0.3.11-py3-none-any.whl", hash = "sha256:95cf602322d43d13cb0fd05cba9bc4cffd7024b10b985d38f599fcc502d2d4d0", size = 60147, upload-time = "2025-03-26T19:59:18.734Z" }, +] + +[[package]] +name = "langchain-text-splitters" +version = "0.3.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/e7/638b44a41e56c3e32cc90cab3622ac2e4c73645252485427d6b2742fcfa8/langchain_text_splitters-0.3.7.tar.gz", hash = "sha256:7dbf0fb98e10bb91792a1d33f540e2287f9cc1dc30ade45b7aedd2d5cd3dc70b", size = 42180, upload-time = "2025-03-18T19:15:42.664Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/85/b7a34b6d34bcc89a2252f5ffea30b94077ba3d7adf72e31b9e04e68c901a/langchain_text_splitters-0.3.7-py3-none-any.whl", hash = "sha256:31ba826013e3f563359d7c7f1e99b1cdb94897f665675ee505718c116e7e20ad", size = 32513, upload-time = "2025-03-18T19:15:41.79Z" }, +] + +[[package]] +name = "langgraph" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/99/26/f01ae40ea26f8c723b6ec186869c80cc04de801630d99943018428b46105/langgraph-0.5.4.tar.gz", hash = "sha256:ab8f6b7b9c50fd2ae35a2efb072fbbfe79500dfc18071ac4ba6f5de5fa181931", size = 443149, upload-time = "2025-07-21T18:20:55.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/82/15184e953234877107bad182b79c9111cb6ce6a79a97fdf36ebcaa11c0d0/langgraph-0.5.4-py3-none-any.whl", hash = "sha256:7122840225623e081be24ac30a691a24e5dac4c0361f593208f912838192d7f6", size = 143942, upload-time = "2025-07-21T18:20:54.442Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "2.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/29/83/6404f6ed23a91d7bc63d7df902d144548434237d017820ceaa8d014035f2/langgraph_checkpoint-2.1.2.tar.gz", hash = "sha256:112e9d067a6eff8937caf198421b1ffba8d9207193f14ac6f89930c1260c06f9", size = 142420, upload-time = "2025-10-07T17:45:17.129Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/f2/06bf5addf8ee664291e1b9ffa1f28fc9d97e59806dc7de5aea9844cbf335/langgraph_checkpoint-2.1.2-py3-none-any.whl", hash = "sha256:911ebffb069fd01775d4b5184c04aaafc2962fcdf50cf49d524cd4367c4d0c60", size = 45763, upload-time = "2025-10-07T17:45:16.19Z" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/8a/91d1bba787c0a8792eb6ef583718a0885b92f1bceec8e229deb2ef02977d/langgraph_prebuilt-0.5.1.tar.gz", hash = "sha256:43a361612b8fb9784338bfc481245e3422ca366ca8e43f68c4c6723d7eb8b9f4", size = 117843, upload-time = "2025-06-27T14:42:03.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/7c/18b74ad8f1a5c8ef7f058dddbef4cd881c25df9620599e32e47fb6c1f829/langgraph_prebuilt-0.5.1-py3-none-any.whl", hash = "sha256:60a752c62a954fab816e9047e1dd05df8f2fabbdf59e1c745d9e2f700202662f", size = 23794, upload-time = "2025-06-27T14:42:03.019Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.1.74" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6d/f7/3807b72988f7eef5e0eb41e7e695eca50f3ed31f7cab5602db3b651c85ff/langgraph_sdk-0.1.74.tar.gz", hash = "sha256:7450e0db5b226cc2e5328ca22c5968725873630ef47c4206a30707cb25dc3ad6", size = 72190, upload-time = "2025-07-21T16:36:50.032Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1f/1a/3eacc4df8127781ee4b0b1e5cad7dbaf12510f58c42cbcb9d1e2dba2a164/langgraph_sdk-0.1.74-py3-none-any.whl", hash = "sha256:3a265c3757fe0048adad4391d10486db63ef7aa5a2cbd22da22d4503554cb890", size = 50254, upload-time = "2025-07-21T16:36:49.134Z" }, +] + +[[package]] +name = "langsmith" +version = "0.3.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/86/b941012013260f95af2e90a3d9415af4a76a003a28412033fc4b09f35731/langsmith-0.3.45.tar.gz", hash = "sha256:1df3c6820c73ed210b2c7bc5cdb7bfa19ddc9126cd03fdf0da54e2e171e6094d", size = 348201, upload-time = "2025-06-05T05:10:28.948Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/f4/c206c0888f8a506404cb4f16ad89593bdc2f70cf00de26a1a0a7a76ad7a3/langsmith-0.3.45-py3-none-any.whl", hash = "sha256:5b55f0518601fa65f3bb6b1a3100379a96aa7b3ed5e9380581615ba9c65ed8ed", size = 363002, upload-time = "2025-06-05T05:10:27.228Z" }, +] + +[[package]] +name = "lomond" +version = "0.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c0/9e/ef7813c910d4a893f2bc763ce9246269f55cc68db21dc1327e376d6a2d02/lomond-0.3.3.tar.gz", hash = "sha256:427936596b144b4ec387ead99aac1560b77c8a78107d3d49415d3abbe79acbd3", size = 28789, upload-time = "2018-09-21T15:17:43.297Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b1/02eebed49c754b01b17de7705caa8c4ceecfb4f926cdafc220c863584360/lomond-0.3.3-py2.py3-none-any.whl", hash = "sha256:df1dd4dd7b802a12b71907ab1abb08b8ce9950195311207579379eb3b1553de7", size = 35512, upload-time = "2018-09-21T15:17:38.686Z" }, +] + +[[package]] +name = "lxml" +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/76/3d/14e82fc7c8fb1b7761f7e748fd47e2ec8276d137b6acfe5a4bb73853e08f/lxml-5.4.0.tar.gz", hash = "sha256:d12832e1dbea4be280b22fd0ea7c9b87f0d8fc51ba06e92dc62d52f804f78ebd", size = 3679479, upload-time = "2025-04-23T01:50:29.322Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/2d/67693cc8a605a12e5975380d7ff83020dcc759351b5a066e1cced04f797b/lxml-5.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:98a3912194c079ef37e716ed228ae0dcb960992100461b704aea4e93af6b0bb9", size = 8083240, upload-time = "2025-04-23T01:45:18.566Z" }, + { url = "https://files.pythonhosted.org/packages/73/53/b5a05ab300a808b72e848efd152fe9c022c0181b0a70b8bca1199f1bed26/lxml-5.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ea0252b51d296a75f6118ed0d8696888e7403408ad42345d7dfd0d1e93309a7", size = 4387685, upload-time = "2025-04-23T01:45:21.387Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/1a3879c5f512bdcd32995c301886fe082b2edd83c87d41b6d42d89b4ea4d/lxml-5.4.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b92b69441d1bd39f4940f9eadfa417a25862242ca2c396b406f9272ef09cdcaa", size = 4991164, upload-time = "2025-04-23T01:45:23.849Z" }, + { url = "https://files.pythonhosted.org/packages/f9/94/bbc66e42559f9d04857071e3b3d0c9abd88579367fd2588a4042f641f57e/lxml-5.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20e16c08254b9b6466526bc1828d9370ee6c0d60a4b64836bc3ac2917d1e16df", size = 4746206, upload-time = "2025-04-23T01:45:26.361Z" }, + { url = "https://files.pythonhosted.org/packages/66/95/34b0679bee435da2d7cae895731700e519a8dfcab499c21662ebe671603e/lxml-5.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7605c1c32c3d6e8c990dd28a0970a3cbbf1429d5b92279e37fda05fb0c92190e", size = 5342144, upload-time = "2025-04-23T01:45:28.939Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5d/abfcc6ab2fa0be72b2ba938abdae1f7cad4c632f8d552683ea295d55adfb/lxml-5.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ecf4c4b83f1ab3d5a7ace10bafcb6f11df6156857a3c418244cef41ca9fa3e44", size = 4825124, upload-time = "2025-04-23T01:45:31.361Z" }, + { url = "https://files.pythonhosted.org/packages/5a/78/6bd33186c8863b36e084f294fc0a5e5eefe77af95f0663ef33809cc1c8aa/lxml-5.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0cef4feae82709eed352cd7e97ae062ef6ae9c7b5dbe3663f104cd2c0e8d94ba", size = 4876520, upload-time = "2025-04-23T01:45:34.191Z" }, + { url = "https://files.pythonhosted.org/packages/3b/74/4d7ad4839bd0fc64e3d12da74fc9a193febb0fae0ba6ebd5149d4c23176a/lxml-5.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:df53330a3bff250f10472ce96a9af28628ff1f4efc51ccba351a8820bca2a8ba", size = 4765016, upload-time = "2025-04-23T01:45:36.7Z" }, + { url = "https://files.pythonhosted.org/packages/24/0d/0a98ed1f2471911dadfc541003ac6dd6879fc87b15e1143743ca20f3e973/lxml-5.4.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:aefe1a7cb852fa61150fcb21a8c8fcea7b58c4cb11fbe59c97a0a4b31cae3c8c", size = 5362884, upload-time = "2025-04-23T01:45:39.291Z" }, + { url = "https://files.pythonhosted.org/packages/48/de/d4f7e4c39740a6610f0f6959052b547478107967362e8424e1163ec37ae8/lxml-5.4.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:ef5a7178fcc73b7d8c07229e89f8eb45b2908a9238eb90dcfc46571ccf0383b8", size = 4902690, upload-time = "2025-04-23T01:45:42.386Z" }, + { url = "https://files.pythonhosted.org/packages/07/8c/61763abd242af84f355ca4ef1ee096d3c1b7514819564cce70fd18c22e9a/lxml-5.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:d2ed1b3cb9ff1c10e6e8b00941bb2e5bb568b307bfc6b17dffbbe8be5eecba86", size = 4944418, upload-time = "2025-04-23T01:45:46.051Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c5/6d7e3b63e7e282619193961a570c0a4c8a57fe820f07ca3fe2f6bd86608a/lxml-5.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:72ac9762a9f8ce74c9eed4a4e74306f2f18613a6b71fa065495a67ac227b3056", size = 4827092, upload-time = "2025-04-23T01:45:48.943Z" }, + { url = "https://files.pythonhosted.org/packages/71/4a/e60a306df54680b103348545706a98a7514a42c8b4fbfdcaa608567bb065/lxml-5.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f5cb182f6396706dc6cc1896dd02b1c889d644c081b0cdec38747573db88a7d7", size = 5418231, upload-time = "2025-04-23T01:45:51.481Z" }, + { url = "https://files.pythonhosted.org/packages/27/f2/9754aacd6016c930875854f08ac4b192a47fe19565f776a64004aa167521/lxml-5.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:3a3178b4873df8ef9457a4875703488eb1622632a9cee6d76464b60e90adbfcd", size = 5261798, upload-time = "2025-04-23T01:45:54.146Z" }, + { url = "https://files.pythonhosted.org/packages/38/a2/0c49ec6941428b1bd4f280650d7b11a0f91ace9db7de32eb7aa23bcb39ff/lxml-5.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e094ec83694b59d263802ed03a8384594fcce477ce484b0cbcd0008a211ca751", size = 4988195, upload-time = "2025-04-23T01:45:56.685Z" }, + { url = "https://files.pythonhosted.org/packages/7a/75/87a3963a08eafc46a86c1131c6e28a4de103ba30b5ae903114177352a3d7/lxml-5.4.0-cp311-cp311-win32.whl", hash = "sha256:4329422de653cdb2b72afa39b0aa04252fca9071550044904b2e7036d9d97fe4", size = 3474243, upload-time = "2025-04-23T01:45:58.863Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f9/1f0964c4f6c2be861c50db380c554fb8befbea98c6404744ce243a3c87ef/lxml-5.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd3be6481ef54b8cfd0e1e953323b7aa9d9789b94842d0e5b142ef4bb7999539", size = 3815197, upload-time = "2025-04-23T01:46:01.096Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/d101ace719ca6a4ec043eb516fcfcb1b396a9fccc4fcd9ef593df34ba0d5/lxml-5.4.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b5aff6f3e818e6bdbbb38e5967520f174b18f539c2b9de867b1e7fde6f8d95a4", size = 8127392, upload-time = "2025-04-23T01:46:04.09Z" }, + { url = "https://files.pythonhosted.org/packages/11/84/beddae0cec4dd9ddf46abf156f0af451c13019a0fa25d7445b655ba5ccb7/lxml-5.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:942a5d73f739ad7c452bf739a62a0f83e2578afd6b8e5406308731f4ce78b16d", size = 4415103, upload-time = "2025-04-23T01:46:07.227Z" }, + { url = "https://files.pythonhosted.org/packages/d0/25/d0d93a4e763f0462cccd2b8a665bf1e4343dd788c76dcfefa289d46a38a9/lxml-5.4.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:460508a4b07364d6abf53acaa0a90b6d370fafde5693ef37602566613a9b0779", size = 5024224, upload-time = "2025-04-23T01:46:10.237Z" }, + { url = "https://files.pythonhosted.org/packages/31/ce/1df18fb8f7946e7f3388af378b1f34fcf253b94b9feedb2cec5969da8012/lxml-5.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529024ab3a505fed78fe3cc5ddc079464e709f6c892733e3f5842007cec8ac6e", size = 4769913, upload-time = "2025-04-23T01:46:12.757Z" }, + { url = "https://files.pythonhosted.org/packages/4e/62/f4a6c60ae7c40d43657f552f3045df05118636be1165b906d3423790447f/lxml-5.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ca56ebc2c474e8f3d5761debfd9283b8b18c76c4fc0967b74aeafba1f5647f9", size = 5290441, upload-time = "2025-04-23T01:46:16.037Z" }, + { url = "https://files.pythonhosted.org/packages/9e/aa/04f00009e1e3a77838c7fc948f161b5d2d5de1136b2b81c712a263829ea4/lxml-5.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a81e1196f0a5b4167a8dafe3a66aa67c4addac1b22dc47947abd5d5c7a3f24b5", size = 4820165, upload-time = "2025-04-23T01:46:19.137Z" }, + { url = "https://files.pythonhosted.org/packages/c9/1f/e0b2f61fa2404bf0f1fdf1898377e5bd1b74cc9b2cf2c6ba8509b8f27990/lxml-5.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00b8686694423ddae324cf614e1b9659c2edb754de617703c3d29ff568448df5", size = 4932580, upload-time = "2025-04-23T01:46:21.963Z" }, + { url = "https://files.pythonhosted.org/packages/24/a2/8263f351b4ffe0ed3e32ea7b7830f845c795349034f912f490180d88a877/lxml-5.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:c5681160758d3f6ac5b4fea370495c48aac0989d6a0f01bb9a72ad8ef5ab75c4", size = 4759493, upload-time = "2025-04-23T01:46:24.316Z" }, + { url = "https://files.pythonhosted.org/packages/05/00/41db052f279995c0e35c79d0f0fc9f8122d5b5e9630139c592a0b58c71b4/lxml-5.4.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:2dc191e60425ad70e75a68c9fd90ab284df64d9cd410ba8d2b641c0c45bc006e", size = 5324679, upload-time = "2025-04-23T01:46:27.097Z" }, + { url = "https://files.pythonhosted.org/packages/1d/be/ee99e6314cdef4587617d3b3b745f9356d9b7dd12a9663c5f3b5734b64ba/lxml-5.4.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:67f779374c6b9753ae0a0195a892a1c234ce8416e4448fe1e9f34746482070a7", size = 4890691, upload-time = "2025-04-23T01:46:30.009Z" }, + { url = "https://files.pythonhosted.org/packages/ad/36/239820114bf1d71f38f12208b9c58dec033cbcf80101cde006b9bde5cffd/lxml-5.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:79d5bfa9c1b455336f52343130b2067164040604e41f6dc4d8313867ed540079", size = 4955075, upload-time = "2025-04-23T01:46:32.33Z" }, + { url = "https://files.pythonhosted.org/packages/d4/e1/1b795cc0b174efc9e13dbd078a9ff79a58728a033142bc6d70a1ee8fc34d/lxml-5.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3d3c30ba1c9b48c68489dc1829a6eede9873f52edca1dda900066542528d6b20", size = 4838680, upload-time = "2025-04-23T01:46:34.852Z" }, + { url = "https://files.pythonhosted.org/packages/72/48/3c198455ca108cec5ae3662ae8acd7fd99476812fd712bb17f1b39a0b589/lxml-5.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1af80c6316ae68aded77e91cd9d80648f7dd40406cef73df841aa3c36f6907c8", size = 5391253, upload-time = "2025-04-23T01:46:37.608Z" }, + { url = "https://files.pythonhosted.org/packages/d6/10/5bf51858971c51ec96cfc13e800a9951f3fd501686f4c18d7d84fe2d6352/lxml-5.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4d885698f5019abe0de3d352caf9466d5de2baded00a06ef3f1216c1a58ae78f", size = 5261651, upload-time = "2025-04-23T01:46:40.183Z" }, + { url = "https://files.pythonhosted.org/packages/2b/11/06710dd809205377da380546f91d2ac94bad9ff735a72b64ec029f706c85/lxml-5.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:aea53d51859b6c64e7c51d522c03cc2c48b9b5d6172126854cc7f01aa11f52bc", size = 5024315, upload-time = "2025-04-23T01:46:43.333Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b0/15b6217834b5e3a59ebf7f53125e08e318030e8cc0d7310355e6edac98ef/lxml-5.4.0-cp312-cp312-win32.whl", hash = "sha256:d90b729fd2732df28130c064aac9bb8aff14ba20baa4aee7bd0795ff1187545f", size = 3486149, upload-time = "2025-04-23T01:46:45.684Z" }, + { url = "https://files.pythonhosted.org/packages/91/1e/05ddcb57ad2f3069101611bd5f5084157d90861a2ef460bf42f45cced944/lxml-5.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1dc4ca99e89c335a7ed47d38964abcb36c5910790f9bd106f2a8fa2ee0b909d2", size = 3817095, upload-time = "2025-04-23T01:46:48.521Z" }, + { url = "https://files.pythonhosted.org/packages/87/cb/2ba1e9dd953415f58548506fa5549a7f373ae55e80c61c9041b7fd09a38a/lxml-5.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:773e27b62920199c6197130632c18fb7ead3257fce1ffb7d286912e56ddb79e0", size = 8110086, upload-time = "2025-04-23T01:46:52.218Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3e/6602a4dca3ae344e8609914d6ab22e52ce42e3e1638c10967568c5c1450d/lxml-5.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ce9c671845de9699904b1e9df95acfe8dfc183f2310f163cdaa91a3535af95de", size = 4404613, upload-time = "2025-04-23T01:46:55.281Z" }, + { url = "https://files.pythonhosted.org/packages/4c/72/bf00988477d3bb452bef9436e45aeea82bb40cdfb4684b83c967c53909c7/lxml-5.4.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9454b8d8200ec99a224df8854786262b1bd6461f4280064c807303c642c05e76", size = 5012008, upload-time = "2025-04-23T01:46:57.817Z" }, + { url = "https://files.pythonhosted.org/packages/92/1f/93e42d93e9e7a44b2d3354c462cd784dbaaf350f7976b5d7c3f85d68d1b1/lxml-5.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cccd007d5c95279e529c146d095f1d39ac05139de26c098166c4beb9374b0f4d", size = 4760915, upload-time = "2025-04-23T01:47:00.745Z" }, + { url = "https://files.pythonhosted.org/packages/45/0b/363009390d0b461cf9976a499e83b68f792e4c32ecef092f3f9ef9c4ba54/lxml-5.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0fce1294a0497edb034cb416ad3e77ecc89b313cff7adbee5334e4dc0d11f422", size = 5283890, upload-time = "2025-04-23T01:47:04.702Z" }, + { url = "https://files.pythonhosted.org/packages/19/dc/6056c332f9378ab476c88e301e6549a0454dbee8f0ae16847414f0eccb74/lxml-5.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24974f774f3a78ac12b95e3a20ef0931795ff04dbb16db81a90c37f589819551", size = 4812644, upload-time = "2025-04-23T01:47:07.833Z" }, + { url = "https://files.pythonhosted.org/packages/ee/8a/f8c66bbb23ecb9048a46a5ef9b495fd23f7543df642dabeebcb2eeb66592/lxml-5.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:497cab4d8254c2a90bf988f162ace2ddbfdd806fce3bda3f581b9d24c852e03c", size = 4921817, upload-time = "2025-04-23T01:47:10.317Z" }, + { url = "https://files.pythonhosted.org/packages/04/57/2e537083c3f381f83d05d9b176f0d838a9e8961f7ed8ddce3f0217179ce3/lxml-5.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:e794f698ae4c5084414efea0f5cc9f4ac562ec02d66e1484ff822ef97c2cadff", size = 4753916, upload-time = "2025-04-23T01:47:12.823Z" }, + { url = "https://files.pythonhosted.org/packages/d8/80/ea8c4072109a350848f1157ce83ccd9439601274035cd045ac31f47f3417/lxml-5.4.0-cp313-cp313-manylinux_2_28_ppc64le.whl", hash = "sha256:2c62891b1ea3094bb12097822b3d44b93fc6c325f2043c4d2736a8ff09e65f60", size = 5289274, upload-time = "2025-04-23T01:47:15.916Z" }, + { url = "https://files.pythonhosted.org/packages/b3/47/c4be287c48cdc304483457878a3f22999098b9a95f455e3c4bda7ec7fc72/lxml-5.4.0-cp313-cp313-manylinux_2_28_s390x.whl", hash = "sha256:142accb3e4d1edae4b392bd165a9abdee8a3c432a2cca193df995bc3886249c8", size = 4874757, upload-time = "2025-04-23T01:47:19.793Z" }, + { url = "https://files.pythonhosted.org/packages/2f/04/6ef935dc74e729932e39478e44d8cfe6a83550552eaa072b7c05f6f22488/lxml-5.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:1a42b3a19346e5601d1b8296ff6ef3d76038058f311902edd574461e9c036982", size = 4947028, upload-time = "2025-04-23T01:47:22.401Z" }, + { url = "https://files.pythonhosted.org/packages/cb/f9/c33fc8daa373ef8a7daddb53175289024512b6619bc9de36d77dca3df44b/lxml-5.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4291d3c409a17febf817259cb37bc62cb7eb398bcc95c1356947e2871911ae61", size = 4834487, upload-time = "2025-04-23T01:47:25.513Z" }, + { url = "https://files.pythonhosted.org/packages/8d/30/fc92bb595bcb878311e01b418b57d13900f84c2b94f6eca9e5073ea756e6/lxml-5.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4f5322cf38fe0e21c2d73901abf68e6329dc02a4994e483adbcf92b568a09a54", size = 5381688, upload-time = "2025-04-23T01:47:28.454Z" }, + { url = "https://files.pythonhosted.org/packages/43/d1/3ba7bd978ce28bba8e3da2c2e9d5ae3f8f521ad3f0ca6ea4788d086ba00d/lxml-5.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0be91891bdb06ebe65122aa6bf3fc94489960cf7e03033c6f83a90863b23c58b", size = 5242043, upload-time = "2025-04-23T01:47:31.208Z" }, + { url = "https://files.pythonhosted.org/packages/ee/cd/95fa2201041a610c4d08ddaf31d43b98ecc4b1d74b1e7245b1abdab443cb/lxml-5.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:15a665ad90054a3d4f397bc40f73948d48e36e4c09f9bcffc7d90c87410e478a", size = 5021569, upload-time = "2025-04-23T01:47:33.805Z" }, + { url = "https://files.pythonhosted.org/packages/2d/a6/31da006fead660b9512d08d23d31e93ad3477dd47cc42e3285f143443176/lxml-5.4.0-cp313-cp313-win32.whl", hash = "sha256:d5663bc1b471c79f5c833cffbc9b87d7bf13f87e055a5c86c363ccd2348d7e82", size = 3485270, upload-time = "2025-04-23T01:47:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/fc/14/c115516c62a7d2499781d2d3d7215218c0731b2c940753bf9f9b7b73924d/lxml-5.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:bcb7a1096b4b6b24ce1ac24d4942ad98f983cd3810f9711bcd0293f43a9d8b9f", size = 3814606, upload-time = "2025-04-23T01:47:39.028Z" }, +] + +[package.optional-dependencies] +html-clean = [ + { name = "lxml-html-clean" }, +] + +[[package]] +name = "lxml-html-clean" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/cb/c9c5bb2a9c47292e236a808dd233a03531f53b626f36259dcd32b49c76da/lxml_html_clean-0.4.3.tar.gz", hash = "sha256:c9df91925b00f836c807beab127aac82575110eacff54d0a75187914f1bd9d8c", size = 21498, upload-time = "2025-10-02T20:49:24.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/4a/63a9540e3ca73709f4200564a737d63a4c8c9c4dd032bab8535f507c190a/lxml_html_clean-0.4.3-py3-none-any.whl", hash = "sha256:63fd7b0b9c3a2e4176611c2ca5d61c4c07ffca2de76c14059a81a2825833731e", size = 14177, upload-time = "2025-10-02T20:49:23.749Z" }, +] + +[[package]] +name = "maincontentextractor" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "html2text" }, + { name = "trafilatura" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/de/634b620e845f48bf27cbe66816e60f0fdb12414f77c8916af60aec508b0d/MainContentExtractor-0.0.4.tar.gz", hash = "sha256:697acc05909fb2f786d9cf7d4ff5bfbf14e4c3359c3a6eadc7ed4403fc2e66e5", size = 5046, upload-time = "2023-12-10T08:05:02.155Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/62/32c33101b179d373d753d7c892b19f2ec22978b6c3c36d17a4a61d2169b6/MainContentExtractor-0.0.4-py3-none-any.whl", hash = "sha256:77684179436e28eb2e19be26657cb2bbd7c1f9213a2c3ee163a8f9dfbca64107", size = 5716, upload-time = "2023-12-10T08:05:00.086Z" }, +] + +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + +[[package]] +name = "markdownify" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "beautifulsoup4" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/78/c48fed23c7aebc2c16049062e72de1da3220c274de59d28c942acdc9ffb2/markdownify-1.1.0.tar.gz", hash = "sha256:449c0bbbf1401c5112379619524f33b63490a8fa479456d41de9dc9e37560ebd", size = 17127, upload-time = "2025-03-05T11:54:40.574Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/11/b751af7ad41b254a802cf52f7bc1fca7cabe2388132f2ce60a1a6b9b9622/markdownify-1.1.0-py3-none-any.whl", hash = "sha256:32a5a08e9af02c8a6528942224c91b933b4bd2c7d078f9012943776fc313eeef", size = 13901, upload-time = "2025-03-05T11:54:39.454Z" }, +] + +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + +[[package]] +name = "marshmallow" +version = "3.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/5e/5e53d26b42ab75491cda89b871dab9e97c840bf12c63ec58a1919710cd06/marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6", size = 221825, upload-time = "2025-02-03T15:32:25.093Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/75/51952c7b2d3873b44a0028b1bd26a25078c18f92f256608e8d1dc61b39fd/marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c", size = 50878, upload-time = "2025-02-03T15:32:22.295Z" }, +] + +[[package]] +name = "mcp" +version = "1.12.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/31/88/f6cb7e7c260cd4b4ce375f2b1614b33ce401f63af0f49f7141a2e9bf0a45/mcp-1.12.4.tar.gz", hash = "sha256:0765585e9a3a5916a3c3ab8659330e493adc7bd8b2ca6120c2d7a0c43e034ca5", size = 431148, upload-time = "2025-08-07T20:31:18.082Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/68/316cbc54b7163fa22571dcf42c9cc46562aae0a021b974e0a8141e897200/mcp-1.12.4-py3-none-any.whl", hash = "sha256:7aa884648969fab8e78b89399d59a683202972e12e6bc9a1c88ce7eda7743789", size = 160145, upload-time = "2025-08-07T20:31:15.69Z" }, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "mem0ai" +version = "0.1.93" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "openai" }, + { name = "posthog" }, + { name = "psycopg2-binary" }, + { name = "pydantic" }, + { name = "pytz" }, + { name = "qdrant-client" }, + { name = "sqlalchemy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/94/e5/95e920e4f74f46a8dea3f0f45fa65a2e7bce8cdbe9fc084fb03c02c9ebf3/mem0ai-0.1.93.tar.gz", hash = "sha256:0c27e8dfb10235f18bf6e1bb007801750664d4c52cafa38e984a0f36b670ec62", size = 88253, upload-time = "2025-04-21T03:56:26.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/e9/ead222a9e11f224f07b7037ebceddfdab6dac4014e37f5a3560f5adb269b/mem0ai-0.1.93-py3-none-any.whl", hash = "sha256:7b8a5fb692fd0db67404f093304b05821eff88f360bba245750c597ae6c72cd3", size = 136765, upload-time = "2025-04-21T03:56:24.489Z" }, +] + +[[package]] +name = "monotonic" +version = "1.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/ca/8e91948b782ddfbd194f323e7e7d9ba12e5877addf04fb2bf8fca38e86ac/monotonic-1.6.tar.gz", hash = "sha256:3a55207bcfed53ddd5c5bae174524062935efed17792e9de2ad0205ce9ad63f7", size = 7615, upload-time = "2021-08-11T14:37:28.79Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/67/7e8406a29b6c45be7af7740456f7f37025f0506ae2e05fb9009a53946860/monotonic-1.6-py2.py3-none-any.whl", hash = "sha256:68687e19a14f11f26d140dd5c86f3dba4bf5df58003000ed467e0e2a69bca96c", size = 8154, upload-time = "2021-04-09T21:58:05.122Z" }, +] + +[[package]] +name = "multidict" +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, + { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, + { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, + { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/d2/86/33272a544eeb36d66e4d9a920602d1a2f57d4ebea4ef3cdfe5a912574c95/multidict-6.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bee7c0588aa0076ce77c0ea5d19a68d76ad81fcd9fe8501003b9a24f9d4000f6", size = 76135, upload-time = "2025-10-06T14:49:54.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/1c/eb97db117a1ebe46d457a3d235a7b9d2e6dcab174f42d1b67663dd9e5371/multidict-6.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7ef6b61cad77091056ce0e7ce69814ef72afacb150b7ac6a3e9470def2198159", size = 45117, upload-time = "2025-10-06T14:49:55.82Z" }, + { url = "https://files.pythonhosted.org/packages/f1/d8/6c3442322e41fb1dd4de8bd67bfd11cd72352ac131f6368315617de752f1/multidict-6.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c0359b1ec12b1d6849c59f9d319610b7f20ef990a6d454ab151aa0e3b9f78ca", size = 43472, upload-time = "2025-10-06T14:49:57.048Z" }, + { url = "https://files.pythonhosted.org/packages/75/3f/e2639e80325af0b6c6febdf8e57cc07043ff15f57fa1ef808f4ccb5ac4cd/multidict-6.7.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cd240939f71c64bd658f186330603aac1a9a81bf6273f523fca63673cb7378a8", size = 249342, upload-time = "2025-10-06T14:49:58.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cc/84e0585f805cbeaa9cbdaa95f9a3d6aed745b9d25700623ac89a6ecff400/multidict-6.7.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60a4d75718a5efa473ebd5ab685786ba0c67b8381f781d1be14da49f1a2dc60", size = 257082, upload-time = "2025-10-06T14:49:59.89Z" }, + { url = "https://files.pythonhosted.org/packages/b0/9c/ac851c107c92289acbbf5cfb485694084690c1b17e555f44952c26ddc5bd/multidict-6.7.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53a42d364f323275126aff81fb67c5ca1b7a04fda0546245730a55c8c5f24bc4", size = 240704, upload-time = "2025-10-06T14:50:01.485Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/5f93e99427248c09da95b62d64b25748a5f5c98c7c2ab09825a1d6af0e15/multidict-6.7.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3b29b980d0ddbecb736735ee5bef69bb2ddca56eff603c86f3f29a1128299b4f", size = 266355, upload-time = "2025-10-06T14:50:02.955Z" }, + { url = "https://files.pythonhosted.org/packages/ec/0c/2ec1d883ceb79c6f7f6d7ad90c919c898f5d1c6ea96d322751420211e072/multidict-6.7.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f8a93b1c0ed2d04b97a5e9336fd2d33371b9a6e29ab7dd6503d63407c20ffbaf", size = 267259, upload-time = "2025-10-06T14:50:04.446Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2d/f0b184fa88d6630aa267680bdb8623fb69cb0d024b8c6f0d23f9a0f406d3/multidict-6.7.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ff96e8815eecacc6645da76c413eb3b3d34cfca256c70b16b286a687d013c32", size = 254903, upload-time = "2025-10-06T14:50:05.98Z" }, + { url = "https://files.pythonhosted.org/packages/06/c9/11ea263ad0df7dfabcad404feb3c0dd40b131bc7f232d5537f2fb1356951/multidict-6.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7516c579652f6a6be0e266aec0acd0db80829ca305c3d771ed898538804c2036", size = 252365, upload-time = "2025-10-06T14:50:07.511Z" }, + { url = "https://files.pythonhosted.org/packages/41/88/d714b86ee2c17d6e09850c70c9d310abac3d808ab49dfa16b43aba9d53fd/multidict-6.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:040f393368e63fb0f3330e70c26bfd336656bed925e5cbe17c9da839a6ab13ec", size = 250062, upload-time = "2025-10-06T14:50:09.074Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/ad407bb9e818c2b31383f6131ca19ea7e35ce93cf1310fce69f12e89de75/multidict-6.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b3bc26a951007b1057a1c543af845f1c7e3e71cc240ed1ace7bf4484aa99196e", size = 249683, upload-time = "2025-10-06T14:50:10.714Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a4/a89abdb0229e533fb925e7c6e5c40201c2873efebc9abaf14046a4536ee6/multidict-6.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:7b022717c748dd1992a83e219587aabe45980d88969f01b316e78683e6285f64", size = 261254, upload-time = "2025-10-06T14:50:12.28Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/0e2b27bd88b40a4fb8dc53dd74eecac70edaa4c1dd0707eb2164da3675b3/multidict-6.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9600082733859f00d79dee64effc7aef1beb26adb297416a4ad2116fd61374bd", size = 257967, upload-time = "2025-10-06T14:50:14.16Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8e/0c67b7120d5d5f6d874ed85a085f9dc770a7f9d8813e80f44a9fec820bb7/multidict-6.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94218fcec4d72bc61df51c198d098ce2b378e0ccbac41ddbed5ef44092913288", size = 250085, upload-time = "2025-10-06T14:50:15.639Z" }, + { url = "https://files.pythonhosted.org/packages/ba/55/b73e1d624ea4b8fd4dd07a3bb70f6e4c7c6c5d9d640a41c6ffe5cdbd2a55/multidict-6.7.0-cp313-cp313-win32.whl", hash = "sha256:a37bd74c3fa9d00be2d7b8eca074dc56bd8077ddd2917a839bd989612671ed17", size = 41713, upload-time = "2025-10-06T14:50:17.066Z" }, + { url = "https://files.pythonhosted.org/packages/32/31/75c59e7d3b4205075b4c183fa4ca398a2daf2303ddf616b04ae6ef55cffe/multidict-6.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:30d193c6cc6d559db42b6bcec8a5d395d34d60c9877a0b71ecd7c204fcf15390", size = 45915, upload-time = "2025-10-06T14:50:18.264Z" }, + { url = "https://files.pythonhosted.org/packages/31/2a/8987831e811f1184c22bc2e45844934385363ee61c0a2dcfa8f71b87e608/multidict-6.7.0-cp313-cp313-win_arm64.whl", hash = "sha256:ea3334cabe4d41b7ccd01e4d349828678794edbc2d3ae97fc162a3312095092e", size = 43077, upload-time = "2025-10-06T14:50:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/e8/68/7b3a5170a382a340147337b300b9eb25a9ddb573bcdfff19c0fa3f31ffba/multidict-6.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad9ce259f50abd98a1ca0aa6e490b58c316a0fce0617f609723e40804add2c00", size = 83114, upload-time = "2025-10-06T14:50:21.223Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/3fa2d07c84df4e302060f555bbf539310980362236ad49f50eeb0a1c1eb9/multidict-6.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:07f5594ac6d084cbb5de2df218d78baf55ef150b91f0ff8a21cc7a2e3a5a58eb", size = 48442, upload-time = "2025-10-06T14:50:22.871Z" }, + { url = "https://files.pythonhosted.org/packages/fc/56/67212d33239797f9bd91962bb899d72bb0f4c35a8652dcdb8ed049bef878/multidict-6.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0591b48acf279821a579282444814a2d8d0af624ae0bc600aa4d1b920b6e924b", size = 46885, upload-time = "2025-10-06T14:50:24.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/d1/908f896224290350721597a61a69cd19b89ad8ee0ae1f38b3f5cd12ea2ac/multidict-6.7.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:749a72584761531d2b9467cfbdfd29487ee21124c304c4b6cb760d8777b27f9c", size = 242588, upload-time = "2025-10-06T14:50:25.716Z" }, + { url = "https://files.pythonhosted.org/packages/ab/67/8604288bbd68680eee0ab568fdcb56171d8b23a01bcd5cb0c8fedf6e5d99/multidict-6.7.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b4c3d199f953acd5b446bf7c0de1fe25d94e09e79086f8dc2f48a11a129cdf1", size = 249966, upload-time = "2025-10-06T14:50:28.192Z" }, + { url = "https://files.pythonhosted.org/packages/20/33/9228d76339f1ba51e3efef7da3ebd91964d3006217aae13211653193c3ff/multidict-6.7.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9fb0211dfc3b51efea2f349ec92c114d7754dd62c01f81c3e32b765b70c45c9b", size = 228618, upload-time = "2025-10-06T14:50:29.82Z" }, + { url = "https://files.pythonhosted.org/packages/f8/2d/25d9b566d10cab1c42b3b9e5b11ef79c9111eaf4463b8c257a3bd89e0ead/multidict-6.7.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a027ec240fe73a8d6281872690b988eed307cd7d91b23998ff35ff577ca688b5", size = 257539, upload-time = "2025-10-06T14:50:31.731Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b1/8d1a965e6637fc33de3c0d8f414485c2b7e4af00f42cab3d84e7b955c222/multidict-6.7.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1d964afecdf3a8288789df2f5751dc0a8261138c3768d9af117ed384e538fad", size = 256345, upload-time = "2025-10-06T14:50:33.26Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/06b5a8adbdeedada6f4fb8d8f193d44a347223b11939b42953eeb6530b6b/multidict-6.7.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:caf53b15b1b7df9fbd0709aa01409000a2b4dd03a5f6f5cc548183c7c8f8b63c", size = 247934, upload-time = "2025-10-06T14:50:34.808Z" }, + { url = "https://files.pythonhosted.org/packages/8f/31/b2491b5fe167ca044c6eb4b8f2c9f3b8a00b24c432c365358eadac5d7625/multidict-6.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:654030da3197d927f05a536a66186070e98765aa5142794c9904555d3a9d8fb5", size = 245243, upload-time = "2025-10-06T14:50:36.436Z" }, + { url = "https://files.pythonhosted.org/packages/61/1a/982913957cb90406c8c94f53001abd9eafc271cb3e70ff6371590bec478e/multidict-6.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:2090d3718829d1e484706a2f525e50c892237b2bf9b17a79b059cb98cddc2f10", size = 235878, upload-time = "2025-10-06T14:50:37.953Z" }, + { url = "https://files.pythonhosted.org/packages/be/c0/21435d804c1a1cf7a2608593f4d19bca5bcbd7a81a70b253fdd1c12af9c0/multidict-6.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d2cfeec3f6f45651b3d408c4acec0ebf3daa9bc8a112a084206f5db5d05b754", size = 243452, upload-time = "2025-10-06T14:50:39.574Z" }, + { url = "https://files.pythonhosted.org/packages/54/0a/4349d540d4a883863191be6eb9a928846d4ec0ea007d3dcd36323bb058ac/multidict-6.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:4ef089f985b8c194d341eb2c24ae6e7408c9a0e2e5658699c92f497437d88c3c", size = 252312, upload-time = "2025-10-06T14:50:41.612Z" }, + { url = "https://files.pythonhosted.org/packages/26/64/d5416038dbda1488daf16b676e4dbfd9674dde10a0cc8f4fc2b502d8125d/multidict-6.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e93a0617cd16998784bf4414c7e40f17a35d2350e5c6f0bd900d3a8e02bd3762", size = 246935, upload-time = "2025-10-06T14:50:43.972Z" }, + { url = "https://files.pythonhosted.org/packages/9f/8c/8290c50d14e49f35e0bd4abc25e1bc7711149ca9588ab7d04f886cdf03d9/multidict-6.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f0feece2ef8ebc42ed9e2e8c78fc4aa3cf455733b507c09ef7406364c94376c6", size = 243385, upload-time = "2025-10-06T14:50:45.648Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/f83ae75e42d694b3fbad3e047670e511c138be747bc713cf1b10d5096416/multidict-6.7.0-cp313-cp313t-win32.whl", hash = "sha256:19a1d55338ec1be74ef62440ca9e04a2f001a04d0cc49a4983dc320ff0f3212d", size = 47777, upload-time = "2025-10-06T14:50:47.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/80/9b174a92814a3830b7357307a792300f42c9e94664b01dee8e457551fa66/multidict-6.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3da4fb467498df97e986af166b12d01f05d2e04f978a9c1c680ea1988e0bc4b6", size = 53104, upload-time = "2025-10-06T14:50:48.851Z" }, + { url = "https://files.pythonhosted.org/packages/cc/28/04baeaf0428d95bb7a7bea0e691ba2f31394338ba424fb0679a9ed0f4c09/multidict-6.7.0-cp313-cp313t-win_arm64.whl", hash = "sha256:b4121773c49a0776461f4a904cdf6264c88e42218aaa8407e803ca8025872792", size = 45503, upload-time = "2025-10-06T14:50:50.16Z" }, + { url = "https://files.pythonhosted.org/packages/e2/b1/3da6934455dd4b261d4c72f897e3a5728eba81db59959f3a639245891baa/multidict-6.7.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bab1e4aff7adaa34410f93b1f8e57c4b36b9af0426a76003f441ee1d3c7e842", size = 75128, upload-time = "2025-10-06T14:50:51.92Z" }, + { url = "https://files.pythonhosted.org/packages/14/2c/f069cab5b51d175a1a2cb4ccdf7a2c2dabd58aa5bd933fa036a8d15e2404/multidict-6.7.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b8512bac933afc3e45fb2b18da8e59b78d4f408399a960339598374d4ae3b56b", size = 44410, upload-time = "2025-10-06T14:50:53.275Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/64bb41266427af6642b6b128e8774ed84c11b80a90702c13ac0a86bb10cc/multidict-6.7.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:79dcf9e477bc65414ebfea98ffd013cb39552b5ecd62908752e0e413d6d06e38", size = 43205, upload-time = "2025-10-06T14:50:54.911Z" }, + { url = "https://files.pythonhosted.org/packages/02/68/6b086fef8a3f1a8541b9236c594f0c9245617c29841f2e0395d979485cde/multidict-6.7.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:31bae522710064b5cbeddaf2e9f32b1abab70ac6ac91d42572502299e9953128", size = 245084, upload-time = "2025-10-06T14:50:56.369Z" }, + { url = "https://files.pythonhosted.org/packages/15/ee/f524093232007cd7a75c1d132df70f235cfd590a7c9eaccd7ff422ef4ae8/multidict-6.7.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a0df7ff02397bb63e2fd22af2c87dfa39e8c7f12947bc524dbdc528282c7e34", size = 252667, upload-time = "2025-10-06T14:50:57.991Z" }, + { url = "https://files.pythonhosted.org/packages/02/a5/eeb3f43ab45878f1895118c3ef157a480db58ede3f248e29b5354139c2c9/multidict-6.7.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7a0222514e8e4c514660e182d5156a415c13ef0aabbd71682fc714e327b95e99", size = 233590, upload-time = "2025-10-06T14:50:59.589Z" }, + { url = "https://files.pythonhosted.org/packages/6a/1e/76d02f8270b97269d7e3dbd45644b1785bda457b474315f8cf999525a193/multidict-6.7.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2397ab4daaf2698eb51a76721e98db21ce4f52339e535725de03ea962b5a3202", size = 264112, upload-time = "2025-10-06T14:51:01.183Z" }, + { url = "https://files.pythonhosted.org/packages/76/0b/c28a70ecb58963847c2a8efe334904cd254812b10e535aefb3bcce513918/multidict-6.7.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8891681594162635948a636c9fe0ff21746aeb3dd5463f6e25d9bea3a8a39ca1", size = 261194, upload-time = "2025-10-06T14:51:02.794Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/2ab26e4209773223159b83aa32721b4021ffb08102f8ac7d689c943fded1/multidict-6.7.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18706cc31dbf402a7945916dd5cddf160251b6dab8a2c5f3d6d5a55949f676b3", size = 248510, upload-time = "2025-10-06T14:51:04.724Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/06c1fa8282af1d1c46fd55c10a7930af652afdce43999501d4d68664170c/multidict-6.7.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f844a1bbf1d207dd311a56f383f7eda2d0e134921d45751842d8235e7778965d", size = 248395, upload-time = "2025-10-06T14:51:06.306Z" }, + { url = "https://files.pythonhosted.org/packages/99/ac/82cb419dd6b04ccf9e7e61befc00c77614fc8134362488b553402ecd55ce/multidict-6.7.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d4393e3581e84e5645506923816b9cc81f5609a778c7e7534054091acc64d1c6", size = 239520, upload-time = "2025-10-06T14:51:08.091Z" }, + { url = "https://files.pythonhosted.org/packages/fa/f3/a0f9bf09493421bd8716a362e0cd1d244f5a6550f5beffdd6b47e885b331/multidict-6.7.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:fbd18dc82d7bf274b37aa48d664534330af744e03bccf696d6f4c6042e7d19e7", size = 245479, upload-time = "2025-10-06T14:51:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/8d/01/476d38fc73a212843f43c852b0eee266b6971f0e28329c2184a8df90c376/multidict-6.7.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b6234e14f9314731ec45c42fc4554b88133ad53a09092cc48a88e771c125dadb", size = 258903, upload-time = "2025-10-06T14:51:12.466Z" }, + { url = "https://files.pythonhosted.org/packages/49/6d/23faeb0868adba613b817d0e69c5f15531b24d462af8012c4f6de4fa8dc3/multidict-6.7.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:08d4379f9744d8f78d98c8673c06e202ffa88296f009c71bbafe8a6bf847d01f", size = 252333, upload-time = "2025-10-06T14:51:14.48Z" }, + { url = "https://files.pythonhosted.org/packages/1e/cc/48d02ac22b30fa247f7dad82866e4b1015431092f4ba6ebc7e77596e0b18/multidict-6.7.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9fe04da3f79387f450fd0061d4dd2e45a72749d31bf634aecc9e27f24fdc4b3f", size = 243411, upload-time = "2025-10-06T14:51:16.072Z" }, + { url = "https://files.pythonhosted.org/packages/4a/03/29a8bf5a18abf1fe34535c88adbdfa88c9fb869b5a3b120692c64abe8284/multidict-6.7.0-cp314-cp314-win32.whl", hash = "sha256:fbafe31d191dfa7c4c51f7a6149c9fb7e914dcf9ffead27dcfd9f1ae382b3885", size = 40940, upload-time = "2025-10-06T14:51:17.544Z" }, + { url = "https://files.pythonhosted.org/packages/82/16/7ed27b680791b939de138f906d5cf2b4657b0d45ca6f5dd6236fdddafb1a/multidict-6.7.0-cp314-cp314-win_amd64.whl", hash = "sha256:2f67396ec0310764b9222a1728ced1ab638f61aadc6226f17a71dd9324f9a99c", size = 45087, upload-time = "2025-10-06T14:51:18.875Z" }, + { url = "https://files.pythonhosted.org/packages/cd/3c/e3e62eb35a1950292fe39315d3c89941e30a9d07d5d2df42965ab041da43/multidict-6.7.0-cp314-cp314-win_arm64.whl", hash = "sha256:ba672b26069957ee369cfa7fc180dde1fc6f176eaf1e6beaf61fbebbd3d9c000", size = 42368, upload-time = "2025-10-06T14:51:20.225Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/cd499bd0dbc5f1136726db3153042a735fffd0d77268e2ee20d5f33c010f/multidict-6.7.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:c1dcc7524066fa918c6a27d61444d4ee7900ec635779058571f70d042d86ed63", size = 82326, upload-time = "2025-10-06T14:51:21.588Z" }, + { url = "https://files.pythonhosted.org/packages/13/8a/18e031eca251c8df76daf0288e6790561806e439f5ce99a170b4af30676b/multidict-6.7.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:27e0b36c2d388dc7b6ced3406671b401e84ad7eb0656b8f3a2f46ed0ce483718", size = 48065, upload-time = "2025-10-06T14:51:22.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/71/5e6701277470a87d234e433fb0a3a7deaf3bcd92566e421e7ae9776319de/multidict-6.7.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2a7baa46a22e77f0988e3b23d4ede5513ebec1929e34ee9495be535662c0dfe2", size = 46475, upload-time = "2025-10-06T14:51:24.352Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6a/bab00cbab6d9cfb57afe1663318f72ec28289ea03fd4e8236bb78429893a/multidict-6.7.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7bf77f54997a9166a2f5675d1201520586439424c2511723a7312bdb4bcc034e", size = 239324, upload-time = "2025-10-06T14:51:25.822Z" }, + { url = "https://files.pythonhosted.org/packages/2a/5f/8de95f629fc22a7769ade8b41028e3e5a822c1f8904f618d175945a81ad3/multidict-6.7.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e011555abada53f1578d63389610ac8a5400fc70ce71156b0aa30d326f1a5064", size = 246877, upload-time = "2025-10-06T14:51:27.604Z" }, + { url = "https://files.pythonhosted.org/packages/23/b4/38881a960458f25b89e9f4a4fdcb02ac101cfa710190db6e5528841e67de/multidict-6.7.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:28b37063541b897fd6a318007373930a75ca6d6ac7c940dbe14731ffdd8d498e", size = 225824, upload-time = "2025-10-06T14:51:29.664Z" }, + { url = "https://files.pythonhosted.org/packages/1e/39/6566210c83f8a261575f18e7144736059f0c460b362e96e9cf797a24b8e7/multidict-6.7.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05047ada7a2fde2631a0ed706f1fd68b169a681dfe5e4cf0f8e4cb6618bbc2cd", size = 253558, upload-time = "2025-10-06T14:51:31.684Z" }, + { url = "https://files.pythonhosted.org/packages/00/a3/67f18315100f64c269f46e6c0319fa87ba68f0f64f2b8e7fd7c72b913a0b/multidict-6.7.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:716133f7d1d946a4e1b91b1756b23c088881e70ff180c24e864c26192ad7534a", size = 252339, upload-time = "2025-10-06T14:51:33.699Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2a/1cb77266afee2458d82f50da41beba02159b1d6b1f7973afc9a1cad1499b/multidict-6.7.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1bed1b467ef657f2a0ae62844a607909ef1c6889562de5e1d505f74457d0b96", size = 244895, upload-time = "2025-10-06T14:51:36.189Z" }, + { url = "https://files.pythonhosted.org/packages/dd/72/09fa7dd487f119b2eb9524946ddd36e2067c08510576d43ff68469563b3b/multidict-6.7.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ca43bdfa5d37bd6aee89d85e1d0831fb86e25541be7e9d376ead1b28974f8e5e", size = 241862, upload-time = "2025-10-06T14:51:41.291Z" }, + { url = "https://files.pythonhosted.org/packages/65/92/bc1f8bd0853d8669300f732c801974dfc3702c3eeadae2f60cef54dc69d7/multidict-6.7.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:44b546bd3eb645fd26fb949e43c02a25a2e632e2ca21a35e2e132c8105dc8599", size = 232376, upload-time = "2025-10-06T14:51:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/ac39399e5cb9d0c2ac8ef6e10a768e4d3bc933ac808d49c41f9dc23337eb/multidict-6.7.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a6ef16328011d3f468e7ebc326f24c1445f001ca1dec335b2f8e66bed3006394", size = 240272, upload-time = "2025-10-06T14:51:45.265Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b6/fed5ac6b8563ec72df6cb1ea8dac6d17f0a4a1f65045f66b6d3bf1497c02/multidict-6.7.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:5aa873cbc8e593d361ae65c68f85faadd755c3295ea2c12040ee146802f23b38", size = 248774, upload-time = "2025-10-06T14:51:46.836Z" }, + { url = "https://files.pythonhosted.org/packages/6b/8d/b954d8c0dc132b68f760aefd45870978deec6818897389dace00fcde32ff/multidict-6.7.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:3d7b6ccce016e29df4b7ca819659f516f0bc7a4b3efa3bb2012ba06431b044f9", size = 242731, upload-time = "2025-10-06T14:51:48.541Z" }, + { url = "https://files.pythonhosted.org/packages/16/9d/a2dac7009125d3540c2f54e194829ea18ac53716c61b655d8ed300120b0f/multidict-6.7.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:171b73bd4ee683d307599b66793ac80981b06f069b62eea1c9e29c9241aa66b0", size = 240193, upload-time = "2025-10-06T14:51:50.355Z" }, + { url = "https://files.pythonhosted.org/packages/39/ca/c05f144128ea232ae2178b008d5011d4e2cea86e4ee8c85c2631b1b94802/multidict-6.7.0-cp314-cp314t-win32.whl", hash = "sha256:b2d7f80c4e1fd010b07cb26820aae86b7e73b681ee4889684fb8d2d4537aab13", size = 48023, upload-time = "2025-10-06T14:51:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8f/0a60e501584145588be1af5cc829265701ba3c35a64aec8e07cbb71d39bb/multidict-6.7.0-cp314-cp314t-win_amd64.whl", hash = "sha256:09929cab6fcb68122776d575e03c6cc64ee0b8fca48d17e135474b042ce515cd", size = 53507, upload-time = "2025-10-06T14:51:53.672Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ae/3148b988a9c6239903e786eac19c889fab607c31d6efa7fb2147e5680f23/multidict-6.7.0-cp314-cp314t-win_arm64.whl", hash = "sha256:cc41db090ed742f32bd2d2c721861725e6109681eddf835d0a82bd3a5c382827", size = 44804, upload-time = "2025-10-06T14:51:55.415Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +] + +[[package]] +name = "mypy-extensions" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, +] + +[[package]] +name = "numpy" +version = "1.26.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.12'", +] +sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" }, + { url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" }, + { url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" }, + { url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" }, + { url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" }, + { url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" }, + { url = "https://files.pythonhosted.org/packages/95/12/8f2020a8e8b8383ac0177dc9570aad031a3beb12e38847f7129bacd96228/numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218", size = 20335901, upload-time = "2024-02-05T23:55:32.801Z" }, + { url = "https://files.pythonhosted.org/packages/75/5b/ca6c8bd14007e5ca171c7c03102d17b4f4e0ceb53957e8c44343a9546dcc/numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b", size = 13685868, upload-time = "2024-02-05T23:55:56.28Z" }, + { url = "https://files.pythonhosted.org/packages/79/f8/97f10e6755e2a7d027ca783f63044d5b1bc1ae7acb12afe6a9b4286eac17/numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b", size = 13925109, upload-time = "2024-02-05T23:56:20.368Z" }, + { url = "https://files.pythonhosted.org/packages/0f/50/de23fde84e45f5c4fda2488c759b69990fd4512387a8632860f3ac9cd225/numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed", size = 17950613, upload-time = "2024-02-05T23:56:56.054Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0c/9c603826b6465e82591e05ca230dfc13376da512b25ccd0894709b054ed0/numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a", size = 13572172, upload-time = "2024-02-05T23:57:21.56Z" }, + { url = "https://files.pythonhosted.org/packages/76/8c/2ba3902e1a0fc1c74962ea9bb33a534bb05984ad7ff9515bf8d07527cadd/numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0", size = 17786643, upload-time = "2024-02-05T23:57:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/28/4a/46d9e65106879492374999e76eb85f87b15328e06bd1550668f79f7b18c6/numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110", size = 5677803, upload-time = "2024-02-05T23:58:08.963Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/86f24451c2d530c88daf997cb8d6ac622c1d40d19f5a031ed68a4b73a374/numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818", size = 15517754, upload-time = "2024-02-05T23:58:36.364Z" }, +] + +[[package]] +name = "numpy" +version = "2.3.4" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14'", + "python_full_version == '3.13.*'", + "python_full_version >= '3.12.4' and python_full_version < '3.13'", + "python_full_version >= '3.12' and python_full_version < '3.12.4'", +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/f4/098d2270d52b41f1bd7db9fc288aaa0400cb48c2a3e2af6fa365d9720947/numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a", size = 20582187, upload-time = "2025-10-15T16:18:11.77Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/e7/0e07379944aa8afb49a556a2b54587b828eb41dc9adc56fb7615b678ca53/numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb", size = 21259519, upload-time = "2025-10-15T16:15:19.012Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cb/5a69293561e8819b09e34ed9e873b9a82b5f2ade23dce4c51dc507f6cfe1/numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f", size = 14452796, upload-time = "2025-10-15T16:15:23.094Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/ff11611200acd602a1e5129e36cfd25bf01ad8e5cf927baf2e90236eb02e/numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36", size = 5381639, upload-time = "2025-10-15T16:15:25.572Z" }, + { url = "https://files.pythonhosted.org/packages/ea/77/e95c757a6fe7a48d28a009267408e8aa382630cc1ad1db7451b3bc21dbb4/numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032", size = 6914296, upload-time = "2025-10-15T16:15:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a3/d2/137c7b6841c942124eae921279e5c41b1c34bab0e6fc60c7348e69afd165/numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7", size = 14591904, upload-time = "2025-10-15T16:15:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/bb/32/67e3b0f07b0aba57a078c4ab777a9e8e6bc62f24fb53a2337f75f9691699/numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda", size = 16939602, upload-time = "2025-10-15T16:15:31.106Z" }, + { url = "https://files.pythonhosted.org/packages/95/22/9639c30e32c93c4cee3ccdb4b09c2d0fbff4dcd06d36b357da06146530fb/numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0", size = 16372661, upload-time = "2025-10-15T16:15:33.546Z" }, + { url = "https://files.pythonhosted.org/packages/12/e9/a685079529be2b0156ae0c11b13d6be647743095bb51d46589e95be88086/numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a", size = 18884682, upload-time = "2025-10-15T16:15:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/cf/85/f6f00d019b0cc741e64b4e00ce865a57b6bed945d1bbeb1ccadbc647959b/numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1", size = 6570076, upload-time = "2025-10-15T16:15:38.225Z" }, + { url = "https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996", size = 13089358, upload-time = "2025-10-15T16:15:40.404Z" }, + { url = "https://files.pythonhosted.org/packages/d1/ad/afdd8351385edf0b3445f9e24210a9c3971ef4de8fd85155462fc4321d79/numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c", size = 10462292, upload-time = "2025-10-15T16:15:42.896Z" }, + { url = "https://files.pythonhosted.org/packages/96/7a/02420400b736f84317e759291b8edaeee9dc921f72b045475a9cbdb26b17/numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11", size = 20957727, upload-time = "2025-10-15T16:15:44.9Z" }, + { url = "https://files.pythonhosted.org/packages/18/90/a014805d627aa5750f6f0e878172afb6454552da929144b3c07fcae1bb13/numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9", size = 14187262, upload-time = "2025-10-15T16:15:47.761Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e4/0a94b09abe89e500dc748e7515f21a13e30c5c3fe3396e6d4ac108c25fca/numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667", size = 5115992, upload-time = "2025-10-15T16:15:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/88/dd/db77c75b055c6157cbd4f9c92c4458daef0dd9cbe6d8d2fe7f803cb64c37/numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef", size = 6648672, upload-time = "2025-10-15T16:15:52.442Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e6/e31b0d713719610e406c0ea3ae0d90760465b086da8783e2fd835ad59027/numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e", size = 14284156, upload-time = "2025-10-15T16:15:54.351Z" }, + { url = "https://files.pythonhosted.org/packages/f9/58/30a85127bfee6f108282107caf8e06a1f0cc997cb6b52cdee699276fcce4/numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a", size = 16641271, upload-time = "2025-10-15T16:15:56.67Z" }, + { url = "https://files.pythonhosted.org/packages/06/f2/2e06a0f2adf23e3ae29283ad96959267938d0efd20a2e25353b70065bfec/numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16", size = 16059531, upload-time = "2025-10-15T16:15:59.412Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e7/b106253c7c0d5dc352b9c8fab91afd76a93950998167fa3e5afe4ef3a18f/numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786", size = 18578983, upload-time = "2025-10-15T16:16:01.804Z" }, + { url = "https://files.pythonhosted.org/packages/73/e3/04ecc41e71462276ee867ccbef26a4448638eadecf1bc56772c9ed6d0255/numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc", size = 6291380, upload-time = "2025-10-15T16:16:03.938Z" }, + { url = "https://files.pythonhosted.org/packages/3d/a8/566578b10d8d0e9955b1b6cd5db4e9d4592dd0026a941ff7994cedda030a/numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32", size = 12787999, upload-time = "2025-10-15T16:16:05.801Z" }, + { url = "https://files.pythonhosted.org/packages/58/22/9c903a957d0a8071b607f5b1bff0761d6e608b9a965945411f867d515db1/numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db", size = 10197412, upload-time = "2025-10-15T16:16:07.854Z" }, + { url = "https://files.pythonhosted.org/packages/57/7e/b72610cc91edf138bc588df5150957a4937221ca6058b825b4725c27be62/numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966", size = 20950335, upload-time = "2025-10-15T16:16:10.304Z" }, + { url = "https://files.pythonhosted.org/packages/3e/46/bdd3370dcea2f95ef14af79dbf81e6927102ddf1cc54adc0024d61252fd9/numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3", size = 14179878, upload-time = "2025-10-15T16:16:12.595Z" }, + { url = "https://files.pythonhosted.org/packages/ac/01/5a67cb785bda60f45415d09c2bc245433f1c68dd82eef9c9002c508b5a65/numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197", size = 5108673, upload-time = "2025-10-15T16:16:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/8428e23a9fcebd33988f4cb61208fda832800ca03781f471f3727a820704/numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e", size = 6641438, upload-time = "2025-10-15T16:16:16.805Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d1/913fe563820f3c6b079f992458f7331278dcd7ba8427e8e745af37ddb44f/numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7", size = 14281290, upload-time = "2025-10-15T16:16:18.764Z" }, + { url = "https://files.pythonhosted.org/packages/9e/7e/7d306ff7cb143e6d975cfa7eb98a93e73495c4deabb7d1b5ecf09ea0fd69/numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953", size = 16636543, upload-time = "2025-10-15T16:16:21.072Z" }, + { url = "https://files.pythonhosted.org/packages/47/6a/8cfc486237e56ccfb0db234945552a557ca266f022d281a2f577b98e955c/numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37", size = 16056117, upload-time = "2025-10-15T16:16:23.369Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0e/42cb5e69ea901e06ce24bfcc4b5664a56f950a70efdcf221f30d9615f3f3/numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd", size = 18577788, upload-time = "2025-10-15T16:16:27.496Z" }, + { url = "https://files.pythonhosted.org/packages/86/92/41c3d5157d3177559ef0a35da50f0cda7fa071f4ba2306dd36818591a5bc/numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646", size = 6282620, upload-time = "2025-10-15T16:16:29.811Z" }, + { url = "https://files.pythonhosted.org/packages/09/97/fd421e8bc50766665ad35536c2bb4ef916533ba1fdd053a62d96cc7c8b95/numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d", size = 12784672, upload-time = "2025-10-15T16:16:31.589Z" }, + { url = "https://files.pythonhosted.org/packages/ad/df/5474fb2f74970ca8eb978093969b125a84cc3d30e47f82191f981f13a8a0/numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc", size = 10196702, upload-time = "2025-10-15T16:16:33.902Z" }, + { url = "https://files.pythonhosted.org/packages/11/83/66ac031464ec1767ea3ed48ce40f615eb441072945e98693bec0bcd056cc/numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879", size = 21049003, upload-time = "2025-10-15T16:16:36.101Z" }, + { url = "https://files.pythonhosted.org/packages/5f/99/5b14e0e686e61371659a1d5bebd04596b1d72227ce36eed121bb0aeab798/numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562", size = 14302980, upload-time = "2025-10-15T16:16:39.124Z" }, + { url = "https://files.pythonhosted.org/packages/2c/44/e9486649cd087d9fc6920e3fc3ac2aba10838d10804b1e179fb7cbc4e634/numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a", size = 5231472, upload-time = "2025-10-15T16:16:41.168Z" }, + { url = "https://files.pythonhosted.org/packages/3e/51/902b24fa8887e5fe2063fd61b1895a476d0bbf46811ab0c7fdf4bd127345/numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6", size = 6739342, upload-time = "2025-10-15T16:16:43.777Z" }, + { url = "https://files.pythonhosted.org/packages/34/f1/4de9586d05b1962acdcdb1dc4af6646361a643f8c864cef7c852bf509740/numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7", size = 14354338, upload-time = "2025-10-15T16:16:46.081Z" }, + { url = "https://files.pythonhosted.org/packages/1f/06/1c16103b425de7969d5a76bdf5ada0804b476fed05d5f9e17b777f1cbefd/numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0", size = 16702392, upload-time = "2025-10-15T16:16:48.455Z" }, + { url = "https://files.pythonhosted.org/packages/34/b2/65f4dc1b89b5322093572b6e55161bb42e3e0487067af73627f795cc9d47/numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f", size = 16134998, upload-time = "2025-10-15T16:16:51.114Z" }, + { url = "https://files.pythonhosted.org/packages/d4/11/94ec578896cdb973aaf56425d6c7f2aff4186a5c00fac15ff2ec46998b46/numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64", size = 18651574, upload-time = "2025-10-15T16:16:53.429Z" }, + { url = "https://files.pythonhosted.org/packages/62/b7/7efa763ab33dbccf56dade36938a77345ce8e8192d6b39e470ca25ff3cd0/numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb", size = 6413135, upload-time = "2025-10-15T16:16:55.992Z" }, + { url = "https://files.pythonhosted.org/packages/43/70/aba4c38e8400abcc2f345e13d972fb36c26409b3e644366db7649015f291/numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c", size = 12928582, upload-time = "2025-10-15T16:16:57.943Z" }, + { url = "https://files.pythonhosted.org/packages/67/63/871fad5f0073fc00fbbdd7232962ea1ac40eeaae2bba66c76214f7954236/numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40", size = 10266691, upload-time = "2025-10-15T16:17:00.048Z" }, + { url = "https://files.pythonhosted.org/packages/72/71/ae6170143c115732470ae3a2d01512870dd16e0953f8a6dc89525696069b/numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e", size = 20955580, upload-time = "2025-10-15T16:17:02.509Z" }, + { url = "https://files.pythonhosted.org/packages/af/39/4be9222ffd6ca8a30eda033d5f753276a9c3426c397bb137d8e19dedd200/numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff", size = 14188056, upload-time = "2025-10-15T16:17:04.873Z" }, + { url = "https://files.pythonhosted.org/packages/6c/3d/d85f6700d0a4aa4f9491030e1021c2b2b7421b2b38d01acd16734a2bfdc7/numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f", size = 5116555, upload-time = "2025-10-15T16:17:07.499Z" }, + { url = "https://files.pythonhosted.org/packages/bf/04/82c1467d86f47eee8a19a464c92f90a9bb68ccf14a54c5224d7031241ffb/numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b", size = 6643581, upload-time = "2025-10-15T16:17:09.774Z" }, + { url = "https://files.pythonhosted.org/packages/0c/d3/c79841741b837e293f48bd7db89d0ac7a4f2503b382b78a790ef1dc778a5/numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7", size = 14299186, upload-time = "2025-10-15T16:17:11.937Z" }, + { url = "https://files.pythonhosted.org/packages/e8/7e/4a14a769741fbf237eec5a12a2cbc7a4c4e061852b6533bcb9e9a796c908/numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2", size = 16638601, upload-time = "2025-10-15T16:17:14.391Z" }, + { url = "https://files.pythonhosted.org/packages/93/87/1c1de269f002ff0a41173fe01dcc925f4ecff59264cd8f96cf3b60d12c9b/numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52", size = 16074219, upload-time = "2025-10-15T16:17:17.058Z" }, + { url = "https://files.pythonhosted.org/packages/cd/28/18f72ee77408e40a76d691001ae599e712ca2a47ddd2c4f695b16c65f077/numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26", size = 18576702, upload-time = "2025-10-15T16:17:19.379Z" }, + { url = "https://files.pythonhosted.org/packages/c3/76/95650169b465ececa8cf4b2e8f6df255d4bf662775e797ade2025cc51ae6/numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc", size = 6337136, upload-time = "2025-10-15T16:17:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/a231a5c43ede5d6f77ba4a91e915a87dea4aeea76560ba4d2bf185c683f0/numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9", size = 12920542, upload-time = "2025-10-15T16:17:24.783Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0c/ae9434a888f717c5ed2ff2393b3f344f0ff6f1c793519fa0c540461dc530/numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868", size = 10480213, upload-time = "2025-10-15T16:17:26.935Z" }, + { url = "https://files.pythonhosted.org/packages/83/4b/c4a5f0841f92536f6b9592694a5b5f68c9ab37b775ff342649eadf9055d3/numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec", size = 21052280, upload-time = "2025-10-15T16:17:29.638Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/90308845fc93b984d2cc96d83e2324ce8ad1fd6efea81b324cba4b673854/numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3", size = 14302930, upload-time = "2025-10-15T16:17:32.384Z" }, + { url = "https://files.pythonhosted.org/packages/3d/4e/07439f22f2a3b247cec4d63a713faae55e1141a36e77fb212881f7cda3fb/numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365", size = 5231504, upload-time = "2025-10-15T16:17:34.515Z" }, + { url = "https://files.pythonhosted.org/packages/ab/de/1e11f2547e2fe3d00482b19721855348b94ada8359aef5d40dd57bfae9df/numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252", size = 6739405, upload-time = "2025-10-15T16:17:36.128Z" }, + { url = "https://files.pythonhosted.org/packages/3b/40/8cd57393a26cebe2e923005db5134a946c62fa56a1087dc7c478f3e30837/numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e", size = 14354866, upload-time = "2025-10-15T16:17:38.884Z" }, + { url = "https://files.pythonhosted.org/packages/93/39/5b3510f023f96874ee6fea2e40dfa99313a00bf3ab779f3c92978f34aace/numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0", size = 16703296, upload-time = "2025-10-15T16:17:41.564Z" }, + { url = "https://files.pythonhosted.org/packages/41/0d/19bb163617c8045209c1996c4e427bccbc4bbff1e2c711f39203c8ddbb4a/numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0", size = 16136046, upload-time = "2025-10-15T16:17:43.901Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c1/6dba12fdf68b02a21ac411c9df19afa66bed2540f467150ca64d246b463d/numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f", size = 18652691, upload-time = "2025-10-15T16:17:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/f8/73/f85056701dbbbb910c51d846c58d29fd46b30eecd2b6ba760fc8b8a1641b/numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d", size = 6485782, upload-time = "2025-10-15T16:17:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/17/90/28fa6f9865181cb817c2471ee65678afa8a7e2a1fb16141473d5fa6bacc3/numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6", size = 13113301, upload-time = "2025-10-15T16:17:50.938Z" }, + { url = "https://files.pythonhosted.org/packages/54/23/08c002201a8e7e1f9afba93b97deceb813252d9cfd0d3351caed123dcf97/numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29", size = 10547532, upload-time = "2025-10-15T16:17:53.48Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b6/64898f51a86ec88ca1257a59c1d7fd077b60082a119affefcdf1dd0df8ca/numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05", size = 21131552, upload-time = "2025-10-15T16:17:55.845Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4c/f135dc6ebe2b6a3c77f4e4838fa63d350f85c99462012306ada1bd4bc460/numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346", size = 14377796, upload-time = "2025-10-15T16:17:58.308Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a4/f33f9c23fcc13dd8412fc8614559b5b797e0aba9d8e01dfa8bae10c84004/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e", size = 5306904, upload-time = "2025-10-15T16:18:00.596Z" }, + { url = "https://files.pythonhosted.org/packages/28/af/c44097f25f834360f9fb960fa082863e0bad14a42f36527b2a121abdec56/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b", size = 6819682, upload-time = "2025-10-15T16:18:02.32Z" }, + { url = "https://files.pythonhosted.org/packages/c5/8c/cd283b54c3c2b77e188f63e23039844f56b23bba1712318288c13fe86baf/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847", size = 14422300, upload-time = "2025-10-15T16:18:04.271Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f0/8404db5098d92446b3e3695cf41c6f0ecb703d701cb0b7566ee2177f2eee/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d", size = 16760806, upload-time = "2025-10-15T16:18:06.668Z" }, + { url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", size = 12973130, upload-time = "2025-10-15T16:18:09.397Z" }, +] + +[[package]] +name = "ollama" +version = "0.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/47/f9ee32467fe92744474a8c72e138113f3b529fc266eea76abfdec9a33f3b/ollama-0.6.0.tar.gz", hash = "sha256:da2b2d846b5944cfbcee1ca1e6ee0585f6c9d45a2fe9467cbcd096a37383da2f", size = 50811, upload-time = "2025-09-24T22:46:02.417Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/c1/edc9f41b425ca40b26b7c104c5f6841a4537bb2552bfa6ca66e81405bb95/ollama-0.6.0-py3-none-any.whl", hash = "sha256:534511b3ccea2dff419ae06c3b58d7f217c55be7897c8ce5868dfb6b219cf7a0", size = 14130, upload-time = "2025-09-24T22:46:01.19Z" }, +] + +[[package]] +name = "openai" +version = "1.109.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/a1/a303104dc55fc546a3f6914c842d3da471c64eec92043aef8f652eb6c524/openai-1.109.1.tar.gz", hash = "sha256:d173ed8dbca665892a6db099b4a2dfac624f94d20a93f46eb0b56aae940ed869", size = 564133, upload-time = "2025-09-24T13:00:53.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/2a/7dd3d207ec669cacc1f186fd856a0f61dbc255d24f6fdc1a6715d6051b0f/openai-1.109.1-py3-none-any.whl", hash = "sha256:6bcaf57086cf59159b8e27447e4e7dd019db5d29a438072fbd49c290c7e65315", size = 948627, upload-time = "2025-09-24T13:00:50.754Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/4d/8df5f83256a809c22c4d6792ce8d43bb503be0fb7a8e4da9025754b09658/orjson-3.11.3.tar.gz", hash = "sha256:1c0603b1d2ffcd43a411d64797a19556ef76958aef1c182f22dc30860152a98a", size = 5482394, upload-time = "2025-08-26T17:46:43.171Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/8b/360674cd817faef32e49276187922a946468579fcaf37afdfb6c07046e92/orjson-3.11.3-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9d2ae0cc6aeb669633e0124531f342a17d8e97ea999e42f12a5ad4adaa304c5f", size = 238238, upload-time = "2025-08-26T17:44:54.214Z" }, + { url = "https://files.pythonhosted.org/packages/05/3d/5fa9ea4b34c1a13be7d9046ba98d06e6feb1d8853718992954ab59d16625/orjson-3.11.3-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:ba21dbb2493e9c653eaffdc38819b004b7b1b246fb77bfc93dc016fe664eac91", size = 127713, upload-time = "2025-08-26T17:44:55.596Z" }, + { url = "https://files.pythonhosted.org/packages/e5/5f/e18367823925e00b1feec867ff5f040055892fc474bf5f7875649ecfa586/orjson-3.11.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00f1a271e56d511d1569937c0447d7dce5a99a33ea0dec76673706360a051904", size = 123241, upload-time = "2025-08-26T17:44:57.185Z" }, + { url = "https://files.pythonhosted.org/packages/0f/bd/3c66b91c4564759cf9f473251ac1650e446c7ba92a7c0f9f56ed54f9f0e6/orjson-3.11.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b67e71e47caa6680d1b6f075a396d04fa6ca8ca09aafb428731da9b3ea32a5a6", size = 127895, upload-time = "2025-08-26T17:44:58.349Z" }, + { url = "https://files.pythonhosted.org/packages/82/b5/dc8dcd609db4766e2967a85f63296c59d4722b39503e5b0bf7fd340d387f/orjson-3.11.3-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d7d012ebddffcce8c85734a6d9e5f08180cd3857c5f5a3ac70185b43775d043d", size = 130303, upload-time = "2025-08-26T17:44:59.491Z" }, + { url = "https://files.pythonhosted.org/packages/48/c2/d58ec5fd1270b2aa44c862171891adc2e1241bd7dab26c8f46eb97c6c6f1/orjson-3.11.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd759f75d6b8d1b62012b7f5ef9461d03c804f94d539a5515b454ba3a6588038", size = 132366, upload-time = "2025-08-26T17:45:00.654Z" }, + { url = "https://files.pythonhosted.org/packages/73/87/0ef7e22eb8dd1ef940bfe3b9e441db519e692d62ed1aae365406a16d23d0/orjson-3.11.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6890ace0809627b0dff19cfad92d69d0fa3f089d3e359a2a532507bb6ba34efb", size = 135180, upload-time = "2025-08-26T17:45:02.424Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6a/e5bf7b70883f374710ad74faf99bacfc4b5b5a7797c1d5e130350e0e28a3/orjson-3.11.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d4a5e041ae435b815e568537755773d05dac031fee6a57b4ba70897a44d9d2", size = 132741, upload-time = "2025-08-26T17:45:03.663Z" }, + { url = "https://files.pythonhosted.org/packages/bd/0c/4577fd860b6386ffaa56440e792af01c7882b56d2766f55384b5b0e9d39b/orjson-3.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d68bf97a771836687107abfca089743885fb664b90138d8761cce61d5625d55", size = 131104, upload-time = "2025-08-26T17:45:04.939Z" }, + { url = "https://files.pythonhosted.org/packages/66/4b/83e92b2d67e86d1c33f2ea9411742a714a26de63641b082bdbf3d8e481af/orjson-3.11.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bfc27516ec46f4520b18ef645864cee168d2a027dbf32c5537cb1f3e3c22dac1", size = 403887, upload-time = "2025-08-26T17:45:06.228Z" }, + { url = "https://files.pythonhosted.org/packages/6d/e5/9eea6a14e9b5ceb4a271a1fd2e1dec5f2f686755c0fab6673dc6ff3433f4/orjson-3.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f66b001332a017d7945e177e282a40b6997056394e3ed7ddb41fb1813b83e824", size = 145855, upload-time = "2025-08-26T17:45:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/45/78/8d4f5ad0c80ba9bf8ac4d0fc71f93a7d0dc0844989e645e2074af376c307/orjson-3.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:212e67806525d2561efbfe9e799633b17eb668b8964abed6b5319b2f1cfbae1f", size = 135361, upload-time = "2025-08-26T17:45:09.625Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5f/16386970370178d7a9b438517ea3d704efcf163d286422bae3b37b88dbb5/orjson-3.11.3-cp311-cp311-win32.whl", hash = "sha256:6e8e0c3b85575a32f2ffa59de455f85ce002b8bdc0662d6b9c2ed6d80ab5d204", size = 136190, upload-time = "2025-08-26T17:45:10.962Z" }, + { url = "https://files.pythonhosted.org/packages/09/60/db16c6f7a41dd8ac9fb651f66701ff2aeb499ad9ebc15853a26c7c152448/orjson-3.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:6be2f1b5d3dc99a5ce5ce162fc741c22ba9f3443d3dd586e6a1211b7bc87bc7b", size = 131389, upload-time = "2025-08-26T17:45:12.285Z" }, + { url = "https://files.pythonhosted.org/packages/3e/2a/bb811ad336667041dea9b8565c7c9faf2f59b47eb5ab680315eea612ef2e/orjson-3.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:fafb1a99d740523d964b15c8db4eabbfc86ff29f84898262bf6e3e4c9e97e43e", size = 126120, upload-time = "2025-08-26T17:45:13.515Z" }, + { url = "https://files.pythonhosted.org/packages/3d/b0/a7edab2a00cdcb2688e1c943401cb3236323e7bfd2839815c6131a3742f4/orjson-3.11.3-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:8c752089db84333e36d754c4baf19c0e1437012242048439c7e80eb0e6426e3b", size = 238259, upload-time = "2025-08-26T17:45:15.093Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/ff4865a9cc398a07a83342713b5932e4dc3cb4bf4bc04e8f83dedfc0d736/orjson-3.11.3-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:9b8761b6cf04a856eb544acdd82fc594b978f12ac3602d6374a7edb9d86fd2c2", size = 127633, upload-time = "2025-08-26T17:45:16.417Z" }, + { url = "https://files.pythonhosted.org/packages/6e/e6/e00bea2d9472f44fe8794f523e548ce0ad51eb9693cf538a753a27b8bda4/orjson-3.11.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b13974dc8ac6ba22feaa867fc19135a3e01a134b4f7c9c28162fed4d615008a", size = 123061, upload-time = "2025-08-26T17:45:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/54/31/9fbb78b8e1eb3ac605467cb846e1c08d0588506028b37f4ee21f978a51d4/orjson-3.11.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f83abab5bacb76d9c821fd5c07728ff224ed0e52d7a71b7b3de822f3df04e15c", size = 127956, upload-time = "2025-08-26T17:45:19.172Z" }, + { url = "https://files.pythonhosted.org/packages/36/88/b0604c22af1eed9f98d709a96302006915cfd724a7ebd27d6dd11c22d80b/orjson-3.11.3-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6fbaf48a744b94091a56c62897b27c31ee2da93d826aa5b207131a1e13d4064", size = 130790, upload-time = "2025-08-26T17:45:20.586Z" }, + { url = "https://files.pythonhosted.org/packages/0e/9d/1c1238ae9fffbfed51ba1e507731b3faaf6b846126a47e9649222b0fd06f/orjson-3.11.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc779b4f4bba2847d0d2940081a7b6f7b5877e05408ffbb74fa1faf4a136c424", size = 132385, upload-time = "2025-08-26T17:45:22.036Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b5/c06f1b090a1c875f337e21dd71943bc9d84087f7cdf8c6e9086902c34e42/orjson-3.11.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd4b909ce4c50faa2192da6bb684d9848d4510b736b0611b6ab4020ea6fd2d23", size = 135305, upload-time = "2025-08-26T17:45:23.4Z" }, + { url = "https://files.pythonhosted.org/packages/a0/26/5f028c7d81ad2ebbf84414ba6d6c9cac03f22f5cd0d01eb40fb2d6a06b07/orjson-3.11.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:524b765ad888dc5518bbce12c77c2e83dee1ed6b0992c1790cc5fb49bb4b6667", size = 132875, upload-time = "2025-08-26T17:45:25.182Z" }, + { url = "https://files.pythonhosted.org/packages/fe/d4/b8df70d9cfb56e385bf39b4e915298f9ae6c61454c8154a0f5fd7efcd42e/orjson-3.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:84fd82870b97ae3cdcea9d8746e592b6d40e1e4d4527835fc520c588d2ded04f", size = 130940, upload-time = "2025-08-26T17:45:27.209Z" }, + { url = "https://files.pythonhosted.org/packages/da/5e/afe6a052ebc1a4741c792dd96e9f65bf3939d2094e8b356503b68d48f9f5/orjson-3.11.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fbecb9709111be913ae6879b07bafd4b0785b44c1eb5cac8ac76da048b3885a1", size = 403852, upload-time = "2025-08-26T17:45:28.478Z" }, + { url = "https://files.pythonhosted.org/packages/f8/90/7bbabafeb2ce65915e9247f14a56b29c9334003536009ef5b122783fe67e/orjson-3.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9dba358d55aee552bd868de348f4736ca5a4086d9a62e2bfbbeeb5629fe8b0cc", size = 146293, upload-time = "2025-08-26T17:45:29.86Z" }, + { url = "https://files.pythonhosted.org/packages/27/b3/2d703946447da8b093350570644a663df69448c9d9330e5f1d9cce997f20/orjson-3.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eabcf2e84f1d7105f84580e03012270c7e97ecb1fb1618bda395061b2a84a049", size = 135470, upload-time = "2025-08-26T17:45:31.243Z" }, + { url = "https://files.pythonhosted.org/packages/38/70/b14dcfae7aff0e379b0119c8a812f8396678919c431efccc8e8a0263e4d9/orjson-3.11.3-cp312-cp312-win32.whl", hash = "sha256:3782d2c60b8116772aea8d9b7905221437fdf53e7277282e8d8b07c220f96cca", size = 136248, upload-time = "2025-08-26T17:45:32.567Z" }, + { url = "https://files.pythonhosted.org/packages/35/b8/9e3127d65de7fff243f7f3e53f59a531bf6bb295ebe5db024c2503cc0726/orjson-3.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:79b44319268af2eaa3e315b92298de9a0067ade6e6003ddaef72f8e0bedb94f1", size = 131437, upload-time = "2025-08-26T17:45:34.949Z" }, + { url = "https://files.pythonhosted.org/packages/51/92/a946e737d4d8a7fd84a606aba96220043dcc7d6988b9e7551f7f6d5ba5ad/orjson-3.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:0e92a4e83341ef79d835ca21b8bd13e27c859e4e9e4d7b63defc6e58462a3710", size = 125978, upload-time = "2025-08-26T17:45:36.422Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/8932b27293ad35919571f77cb3693b5906cf14f206ef17546052a241fdf6/orjson-3.11.3-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:af40c6612fd2a4b00de648aa26d18186cd1322330bd3a3cc52f87c699e995810", size = 238127, upload-time = "2025-08-26T17:45:38.146Z" }, + { url = "https://files.pythonhosted.org/packages/1c/82/cb93cd8cf132cd7643b30b6c5a56a26c4e780c7a145db6f83de977b540ce/orjson-3.11.3-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:9f1587f26c235894c09e8b5b7636a38091a9e6e7fe4531937534749c04face43", size = 127494, upload-time = "2025-08-26T17:45:39.57Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b8/2d9eb181a9b6bb71463a78882bcac1027fd29cf62c38a40cc02fc11d3495/orjson-3.11.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61dcdad16da5bb486d7227a37a2e789c429397793a6955227cedbd7252eb5a27", size = 123017, upload-time = "2025-08-26T17:45:40.876Z" }, + { url = "https://files.pythonhosted.org/packages/b4/14/a0e971e72d03b509190232356d54c0f34507a05050bd026b8db2bf2c192c/orjson-3.11.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11c6d71478e2cbea0a709e8a06365fa63da81da6498a53e4c4f065881d21ae8f", size = 127898, upload-time = "2025-08-26T17:45:42.188Z" }, + { url = "https://files.pythonhosted.org/packages/8e/af/dc74536722b03d65e17042cc30ae586161093e5b1f29bccda24765a6ae47/orjson-3.11.3-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff94112e0098470b665cb0ed06efb187154b63649403b8d5e9aedeb482b4548c", size = 130742, upload-time = "2025-08-26T17:45:43.511Z" }, + { url = "https://files.pythonhosted.org/packages/62/e6/7a3b63b6677bce089fe939353cda24a7679825c43a24e49f757805fc0d8a/orjson-3.11.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae8b756575aaa2a855a75192f356bbda11a89169830e1439cfb1a3e1a6dde7be", size = 132377, upload-time = "2025-08-26T17:45:45.525Z" }, + { url = "https://files.pythonhosted.org/packages/fc/cd/ce2ab93e2e7eaf518f0fd15e3068b8c43216c8a44ed82ac2b79ce5cef72d/orjson-3.11.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c9416cc19a349c167ef76135b2fe40d03cea93680428efee8771f3e9fb66079d", size = 135313, upload-time = "2025-08-26T17:45:46.821Z" }, + { url = "https://files.pythonhosted.org/packages/d0/b4/f98355eff0bd1a38454209bbc73372ce351ba29933cb3e2eba16c04b9448/orjson-3.11.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b822caf5b9752bc6f246eb08124c3d12bf2175b66ab74bac2ef3bbf9221ce1b2", size = 132908, upload-time = "2025-08-26T17:45:48.126Z" }, + { url = "https://files.pythonhosted.org/packages/eb/92/8f5182d7bc2a1bed46ed960b61a39af8389f0ad476120cd99e67182bfb6d/orjson-3.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:414f71e3bdd5573893bf5ecdf35c32b213ed20aa15536fe2f588f946c318824f", size = 130905, upload-time = "2025-08-26T17:45:49.414Z" }, + { url = "https://files.pythonhosted.org/packages/1a/60/c41ca753ce9ffe3d0f67b9b4c093bdd6e5fdb1bc53064f992f66bb99954d/orjson-3.11.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:828e3149ad8815dc14468f36ab2a4b819237c155ee1370341b91ea4c8672d2ee", size = 403812, upload-time = "2025-08-26T17:45:51.085Z" }, + { url = "https://files.pythonhosted.org/packages/dd/13/e4a4f16d71ce1868860db59092e78782c67082a8f1dc06a3788aef2b41bc/orjson-3.11.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac9e05f25627ffc714c21f8dfe3a579445a5c392a9c8ae7ba1d0e9fb5333f56e", size = 146277, upload-time = "2025-08-26T17:45:52.851Z" }, + { url = "https://files.pythonhosted.org/packages/8d/8b/bafb7f0afef9344754a3a0597a12442f1b85a048b82108ef2c956f53babd/orjson-3.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e44fbe4000bd321d9f3b648ae46e0196d21577cf66ae684a96ff90b1f7c93633", size = 135418, upload-time = "2025-08-26T17:45:54.806Z" }, + { url = "https://files.pythonhosted.org/packages/60/d4/bae8e4f26afb2c23bea69d2f6d566132584d1c3a5fe89ee8c17b718cab67/orjson-3.11.3-cp313-cp313-win32.whl", hash = "sha256:2039b7847ba3eec1f5886e75e6763a16e18c68a63efc4b029ddf994821e2e66b", size = 136216, upload-time = "2025-08-26T17:45:57.182Z" }, + { url = "https://files.pythonhosted.org/packages/88/76/224985d9f127e121c8cad882cea55f0ebe39f97925de040b75ccd4b33999/orjson-3.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:29be5ac4164aa8bdcba5fa0700a3c9c316b411d8ed9d39ef8a882541bd452fae", size = 131362, upload-time = "2025-08-26T17:45:58.56Z" }, + { url = "https://files.pythonhosted.org/packages/e2/cf/0dce7a0be94bd36d1346be5067ed65ded6adb795fdbe3abd234c8d576d01/orjson-3.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:18bd1435cb1f2857ceb59cfb7de6f92593ef7b831ccd1b9bfb28ca530e539dce", size = 125989, upload-time = "2025-08-26T17:45:59.95Z" }, + { url = "https://files.pythonhosted.org/packages/ef/77/d3b1fef1fc6aaeed4cbf3be2b480114035f4df8fa1a99d2dac1d40d6e924/orjson-3.11.3-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:cf4b81227ec86935568c7edd78352a92e97af8da7bd70bdfdaa0d2e0011a1ab4", size = 238115, upload-time = "2025-08-26T17:46:01.669Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6d/468d21d49bb12f900052edcfbf52c292022d0a323d7828dc6376e6319703/orjson-3.11.3-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:bc8bc85b81b6ac9fc4dae393a8c159b817f4c2c9dee5d12b773bddb3b95fc07e", size = 127493, upload-time = "2025-08-26T17:46:03.466Z" }, + { url = "https://files.pythonhosted.org/packages/67/46/1e2588700d354aacdf9e12cc2d98131fb8ac6f31ca65997bef3863edb8ff/orjson-3.11.3-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:88dcfc514cfd1b0de038443c7b3e6a9797ffb1b3674ef1fd14f701a13397f82d", size = 122998, upload-time = "2025-08-26T17:46:04.803Z" }, + { url = "https://files.pythonhosted.org/packages/3b/94/11137c9b6adb3779f1b34fd98be51608a14b430dbc02c6d41134fbba484c/orjson-3.11.3-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d61cd543d69715d5fc0a690c7c6f8dcc307bc23abef9738957981885f5f38229", size = 132915, upload-time = "2025-08-26T17:46:06.237Z" }, + { url = "https://files.pythonhosted.org/packages/10/61/dccedcf9e9bcaac09fdabe9eaee0311ca92115699500efbd31950d878833/orjson-3.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:2b7b153ed90ababadbef5c3eb39549f9476890d339cf47af563aea7e07db2451", size = 130907, upload-time = "2025-08-26T17:46:07.581Z" }, + { url = "https://files.pythonhosted.org/packages/0e/fd/0e935539aa7b08b3ca0f817d73034f7eb506792aae5ecc3b7c6e679cdf5f/orjson-3.11.3-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:7909ae2460f5f494fecbcd10613beafe40381fd0316e35d6acb5f3a05bfda167", size = 403852, upload-time = "2025-08-26T17:46:08.982Z" }, + { url = "https://files.pythonhosted.org/packages/4a/2b/50ae1a5505cd1043379132fdb2adb8a05f37b3e1ebffe94a5073321966fd/orjson-3.11.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:2030c01cbf77bc67bee7eef1e7e31ecf28649353987775e3583062c752da0077", size = 146309, upload-time = "2025-08-26T17:46:10.576Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1d/a473c158e380ef6f32753b5f39a69028b25ec5be331c2049a2201bde2e19/orjson-3.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a0169ebd1cbd94b26c7a7ad282cf5c2744fce054133f959e02eb5265deae1872", size = 135424, upload-time = "2025-08-26T17:46:12.386Z" }, + { url = "https://files.pythonhosted.org/packages/da/09/17d9d2b60592890ff7382e591aa1d9afb202a266b180c3d4049b1ec70e4a/orjson-3.11.3-cp314-cp314-win32.whl", hash = "sha256:0c6d7328c200c349e3a4c6d8c83e0a5ad029bdc2d417f234152bf34842d0fc8d", size = 136266, upload-time = "2025-08-26T17:46:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/15/58/358f6846410a6b4958b74734727e582ed971e13d335d6c7ce3e47730493e/orjson-3.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:317bbe2c069bbc757b1a2e4105b64aacd3bc78279b66a6b9e51e846e4809f804", size = 131351, upload-time = "2025-08-26T17:46:15.27Z" }, + { url = "https://files.pythonhosted.org/packages/28/01/d6b274a0635be0468d4dbd9cafe80c47105937a0d42434e805e67cd2ed8b/orjson-3.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:e8f6a7a27d7b7bec81bd5924163e9af03d49bbb63013f107b48eb5d16db711bc", size = 125985, upload-time = "2025-08-26T17:46:16.67Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/f8/224c342c0e03e131aaa1a1f19aa2244e167001783a433f4eed10eedd834b/ormsgpack-1.11.0.tar.gz", hash = "sha256:7c9988e78fedba3292541eb3bb274fa63044ef4da2ddb47259ea70c05dee4206", size = 49357, upload-time = "2025-10-08T17:29:15.621Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/7c/90164d00e8e94b48eff8a17bc2f4be6b71ae356a00904bc69d5e8afe80fb/ormsgpack-1.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c7be823f47d8e36648d4bc90634b93f02b7d7cc7480081195f34767e86f181fb", size = 367964, upload-time = "2025-10-08T17:28:16.778Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c2/fb6331e880a3446c1341e72c77bd5a46da3e92a8e2edf7ea84a4c6c14fff/ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68accf15d1b013812755c0eb7a30e1fc2f81eb603a1a143bf0cda1b301cfa797", size = 195209, upload-time = "2025-10-08T17:28:17.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/50/4943fb5df8cc02da6b7b1ee2c2a7fb13aebc9f963d69280b1bb02b1fb178/ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:805d06fb277d9a4e503c0c707545b49cde66cbb2f84e5cf7c58d81dfc20d8658", size = 205869, upload-time = "2025-10-08T17:28:19.01Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/e7e06835bfea9adeef43915143ce818098aecab0cbd3df584815adf3e399/ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1e57cdf003e77acc43643bda151dc01f97147a64b11cdee1380bb9698a7601c", size = 207391, upload-time = "2025-10-08T17:28:20.352Z" }, + { url = "https://files.pythonhosted.org/packages/33/f0/f28a19e938a14ec223396e94f4782fbcc023f8c91f2ab6881839d3550f32/ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37fc05bdaabd994097c62e2f3e08f66b03f856a640ede6dc5ea340bd15b77f4d", size = 377081, upload-time = "2025-10-08T17:28:21.926Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e3/73d1d7287637401b0b6637e30ba9121e1aa1d9f5ea185ed9834ca15d512c/ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a6e9db6c73eb46b2e4d97bdffd1368a66f54e6806b563a997b19c004ef165e1d", size = 470779, upload-time = "2025-10-08T17:28:22.993Z" }, + { url = "https://files.pythonhosted.org/packages/9c/46/7ba7f9721e766dd0dfe4cedf444439447212abffe2d2f4538edeeec8ccbd/ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9c44eae5ac0196ffc8b5ed497c75511056508f2303fa4d36b208eb820cf209e", size = 380865, upload-time = "2025-10-08T17:28:24.012Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7d/bb92a0782bbe0626c072c0320001410cf3f6743ede7dc18f034b1a18edef/ormsgpack-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:11d0dfaf40ae7c6de4f7dbd1e4892e2e6a55d911ab1774357c481158d17371e4", size = 112058, upload-time = "2025-10-08T17:28:25.015Z" }, + { url = "https://files.pythonhosted.org/packages/28/1a/f07c6f74142815d67e1d9d98c5b2960007100408ade8242edac96d5d1c73/ormsgpack-1.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:0c63a3f7199a3099c90398a1bdf0cb577b06651a442dc5efe67f2882665e5b02", size = 105894, upload-time = "2025-10-08T17:28:25.93Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/2805ebfb3d2cbb6c661b5fae053960fc90a2611d0d93e2207e753e836117/ormsgpack-1.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3434d0c8d67de27d9010222de07fb6810fb9af3bb7372354ffa19257ac0eb83b", size = 368474, upload-time = "2025-10-08T17:28:27.532Z" }, + { url = "https://files.pythonhosted.org/packages/6f/39/6afae47822dca0ce4465d894c0bbb860a850ce29c157882dbdf77a5dd26e/ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2da5bd097e8dbfa4eb0d4ccfe79acd6f538dee4493579e2debfe4fc8f4ca89b", size = 195321, upload-time = "2025-10-08T17:28:28.573Z" }, + { url = "https://files.pythonhosted.org/packages/f6/54/11eda6b59f696d2f16de469bfbe539c9f469c4b9eef5a513996b5879c6e9/ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fdbaa0a5a8606a486960b60c24f2d5235d30ac7a8b98eeaea9854bffef14dc3d", size = 206036, upload-time = "2025-10-08T17:28:29.785Z" }, + { url = "https://files.pythonhosted.org/packages/1e/86/890430f704f84c4699ddad61c595d171ea2fd77a51fbc106f83981e83939/ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3682f24f800c1837017ee90ce321086b2cbaef88db7d4cdbbda1582aa6508159", size = 207615, upload-time = "2025-10-08T17:28:31.076Z" }, + { url = "https://files.pythonhosted.org/packages/b6/b9/77383e16c991c0ecb772205b966fc68d9c519e0b5f9c3913283cbed30ffe/ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcca21202bb05ccbf3e0e92f560ee59b9331182e4c09c965a28155efbb134993", size = 377195, upload-time = "2025-10-08T17:28:32.436Z" }, + { url = "https://files.pythonhosted.org/packages/20/e2/15f9f045d4947f3c8a5e0535259fddf027b17b1215367488b3565c573b9d/ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c30e5c4655ba46152d722ec7468e8302195e6db362ec1ae2c206bc64f6030e43", size = 470960, upload-time = "2025-10-08T17:28:33.556Z" }, + { url = "https://files.pythonhosted.org/packages/b8/61/403ce188c4c495bc99dff921a0ad3d9d352dd6d3c4b629f3638b7f0cf79b/ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7138a341f9e2c08c59368f03d3be25e8b87b3baaf10d30fb1f6f6b52f3d47944", size = 381174, upload-time = "2025-10-08T17:28:34.781Z" }, + { url = "https://files.pythonhosted.org/packages/14/a8/94c94bc48c68da4374870a851eea03fc5a45eb041182ad4c5ed9acfc05a4/ormsgpack-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4bd8589b78a11026d47f4edf13c1ceab9088bb12451f34396afe6497db28a27", size = 112314, upload-time = "2025-10-08T17:28:36.259Z" }, + { url = "https://files.pythonhosted.org/packages/19/d0/aa4cf04f04e4cc180ce7a8d8ddb5a7f3af883329cbc59645d94d3ba157a5/ormsgpack-1.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:e5e746a1223e70f111d4001dab9585ac8639eee8979ca0c8db37f646bf2961da", size = 106072, upload-time = "2025-10-08T17:28:37.518Z" }, + { url = "https://files.pythonhosted.org/packages/8b/35/e34722edb701d053cf2240f55974f17b7dbfd11fdef72bd2f1835bcebf26/ormsgpack-1.11.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e7b36ab7b45cb95217ae1f05f1318b14a3e5ef73cb00804c0f06233f81a14e8", size = 368502, upload-time = "2025-10-08T17:28:38.547Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6a/c2fc369a79d6aba2aa28c8763856c95337ac7fcc0b2742185cd19397212a/ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43402d67e03a9a35cc147c8c03f0c377cad016624479e1ee5b879b8425551484", size = 195344, upload-time = "2025-10-08T17:28:39.554Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6a/0f8e24b7489885534c1a93bdba7c7c434b9b8638713a68098867db9f254c/ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64fd992f932764d6306b70ddc755c1bc3405c4c6a69f77a36acf7af1c8f5ada4", size = 206045, upload-time = "2025-10-08T17:28:40.561Z" }, + { url = "https://files.pythonhosted.org/packages/99/71/8b460ba264f3c6f82ef5b1920335720094e2bd943057964ce5287d6df83a/ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0362fb7fe4a29c046c8ea799303079a09372653a1ce5a5a588f3bbb8088368d0", size = 207641, upload-time = "2025-10-08T17:28:41.736Z" }, + { url = "https://files.pythonhosted.org/packages/50/cf/f369446abaf65972424ed2651f2df2b7b5c3b735c93fc7fa6cfb81e34419/ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:de2f7a65a9d178ed57be49eba3d0fc9b833c32beaa19dbd4ba56014d3c20b152", size = 377211, upload-time = "2025-10-08T17:28:43.12Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3f/948bb0047ce0f37c2efc3b9bb2bcfdccc61c63e0b9ce8088d4903ba39dcf/ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f38cfae95461466055af966fc922d06db4e1654966385cda2828653096db34da", size = 470973, upload-time = "2025-10-08T17:28:44.465Z" }, + { url = "https://files.pythonhosted.org/packages/31/a4/92a8114d1d017c14aaa403445060f345df9130ca532d538094f38e535988/ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c88396189d238f183cea7831b07a305ab5c90d6d29b53288ae11200bd956357b", size = 381161, upload-time = "2025-10-08T17:28:46.063Z" }, + { url = "https://files.pythonhosted.org/packages/d0/64/5b76447da654798bfcfdfd64ea29447ff2b7f33fe19d0e911a83ad5107fc/ormsgpack-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5403d1a945dd7c81044cebeca3f00a28a0f4248b33242a5d2d82111628043725", size = 112321, upload-time = "2025-10-08T17:28:47.393Z" }, + { url = "https://files.pythonhosted.org/packages/46/5e/89900d06db9ab81e7ec1fd56a07c62dfbdcda398c435718f4252e1dc52a0/ormsgpack-1.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c57357b8d43b49722b876edf317bdad9e6d52071b523fdd7394c30cd1c67d5a0", size = 106084, upload-time = "2025-10-08T17:28:48.305Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0b/c659e8657085c8c13f6a0224789f422620cef506e26573b5434defe68483/ormsgpack-1.11.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d390907d90fd0c908211592c485054d7a80990697ef4dff4e436ac18e1aab98a", size = 368497, upload-time = "2025-10-08T17:28:49.297Z" }, + { url = "https://files.pythonhosted.org/packages/1b/0e/451e5848c7ed56bd287e8a2b5cb5926e54466f60936e05aec6cb299f9143/ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6153c2e92e789509098e04c9aa116b16673bd88ec78fbe0031deeb34ab642d10", size = 195385, upload-time = "2025-10-08T17:28:50.314Z" }, + { url = "https://files.pythonhosted.org/packages/4c/28/90f78cbbe494959f2439c2ec571f08cd3464c05a6a380b0d621c622122a9/ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2b2c2a065a94d742212b2018e1fecd8f8d72f3c50b53a97d1f407418093446d", size = 206114, upload-time = "2025-10-08T17:28:51.336Z" }, + { url = "https://files.pythonhosted.org/packages/fb/db/34163f4c0923bea32dafe42cd878dcc66795a3e85669bc4b01c1e2b92a7b/ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:110e65b5340f3d7ef8b0009deae3c6b169437e6b43ad5a57fd1748085d29d2ac", size = 207679, upload-time = "2025-10-08T17:28:53.627Z" }, + { url = "https://files.pythonhosted.org/packages/b6/14/04ee741249b16f380a9b4a0cc19d4134d0b7c74bab27a2117da09e525eb9/ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c27e186fca96ab34662723e65b420919910acbbc50fc8e1a44e08f26268cb0e0", size = 377237, upload-time = "2025-10-08T17:28:56.12Z" }, + { url = "https://files.pythonhosted.org/packages/89/ff/53e588a6aaa833237471caec679582c2950f0e7e1a8ba28c1511b465c1f4/ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d56b1f877c13d499052d37a3db2378a97d5e1588d264f5040b3412aee23d742c", size = 471021, upload-time = "2025-10-08T17:28:57.299Z" }, + { url = "https://files.pythonhosted.org/packages/a6/f9/f20a6d9ef2be04da3aad05e8f5699957e9a30c6d5c043a10a296afa7e890/ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c88e28cd567c0a3269f624b4ade28142d5e502c8e826115093c572007af5be0a", size = 381205, upload-time = "2025-10-08T17:28:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/f8/64/96c07d084b479ac8b7821a77ffc8d3f29d8b5c95ebfdf8db1c03dff02762/ormsgpack-1.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:8811160573dc0a65f62f7e0792c4ca6b7108dfa50771edb93f9b84e2d45a08ae", size = 112374, upload-time = "2025-10-08T17:29:00Z" }, + { url = "https://files.pythonhosted.org/packages/88/a5/5dcc18b818d50213a3cadfe336bb6163a102677d9ce87f3d2f1a1bee0f8c/ormsgpack-1.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:23e30a8d3c17484cf74e75e6134322255bd08bc2b5b295cc9c442f4bae5f3c2d", size = 106056, upload-time = "2025-10-08T17:29:01.29Z" }, + { url = "https://files.pythonhosted.org/packages/19/2b/776d1b411d2be50f77a6e6e94a25825cca55dcacfe7415fd691a144db71b/ormsgpack-1.11.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2905816502adfaf8386a01dd85f936cd378d243f4f5ee2ff46f67f6298dc90d5", size = 368661, upload-time = "2025-10-08T17:29:02.382Z" }, + { url = "https://files.pythonhosted.org/packages/a9/0c/81a19e6115b15764db3d241788f9fac093122878aaabf872cc545b0c4650/ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04402fb9a0a9b9f18fbafd6d5f8398ee99b3ec619fb63952d3a954bc9d47daa", size = 195539, upload-time = "2025-10-08T17:29:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/97/86/e5b50247a61caec5718122feb2719ea9d451d30ac0516c288c1dbc6408e8/ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a025ec07ac52056ecfd9e57b5cbc6fff163f62cb9805012b56cda599157f8ef2", size = 207718, upload-time = "2025-10-08T17:29:04.545Z" }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213, upload-time = "2024-09-20T13:10:04.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222, upload-time = "2024-09-20T13:08:56.254Z" }, + { url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274, upload-time = "2024-09-20T13:08:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836, upload-time = "2024-09-20T19:01:57.571Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505, upload-time = "2024-09-20T13:09:01.501Z" }, + { url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420, upload-time = "2024-09-20T19:02:00.678Z" }, + { url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457, upload-time = "2024-09-20T13:09:04.105Z" }, + { url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166, upload-time = "2024-09-20T13:09:06.917Z" }, + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893, upload-time = "2024-09-20T13:09:09.655Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475, upload-time = "2024-09-20T13:09:14.718Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645, upload-time = "2024-09-20T19:02:03.88Z" }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445, upload-time = "2024-09-20T13:09:17.621Z" }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235, upload-time = "2024-09-20T19:02:07.094Z" }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756, upload-time = "2024-09-20T13:09:20.474Z" }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248, upload-time = "2024-09-20T13:09:23.137Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643, upload-time = "2024-09-20T13:09:25.522Z" }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573, upload-time = "2024-09-20T13:09:28.012Z" }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085, upload-time = "2024-09-20T19:02:10.451Z" }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809, upload-time = "2024-09-20T13:09:30.814Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316, upload-time = "2024-09-20T19:02:13.825Z" }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055, upload-time = "2024-09-20T13:09:33.462Z" }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175, upload-time = "2024-09-20T13:09:35.871Z" }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650, upload-time = "2024-09-20T13:09:38.685Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177, upload-time = "2024-09-20T13:09:41.141Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526, upload-time = "2024-09-20T19:02:16.905Z" }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013, upload-time = "2024-09-20T13:09:44.39Z" }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620, upload-time = "2024-09-20T19:02:20.639Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436, upload-time = "2024-09-20T13:09:48.112Z" }, +] + +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, + { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, + { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, + { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, + { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, + { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, + { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, + { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, + { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, + { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, + { url = "https://files.pythonhosted.org/packages/1e/93/0952f2ed8db3a5a4c7a11f91965d6184ebc8cd7cbb7941a260d5f018cd2d/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd", size = 2128328, upload-time = "2025-07-01T09:14:35.276Z" }, + { url = "https://files.pythonhosted.org/packages/4b/e8/100c3d114b1a0bf4042f27e0f87d2f25e857e838034e98ca98fe7b8c0a9c/pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8", size = 2170652, upload-time = "2025-07-01T09:14:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/aa/86/3f758a28a6e381758545f7cdb4942e1cb79abd271bea932998fc0db93cb6/pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f", size = 2227443, upload-time = "2025-07-01T09:14:39.344Z" }, + { url = "https://files.pythonhosted.org/packages/01/f4/91d5b3ffa718df2f53b0dc109877993e511f4fd055d7e9508682e8aba092/pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c", size = 5278474, upload-time = "2025-07-01T09:14:41.843Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0e/37d7d3eca6c879fbd9dba21268427dffda1ab00d4eb05b32923d4fbe3b12/pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd", size = 4686038, upload-time = "2025-07-01T09:14:44.008Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b0/3426e5c7f6565e752d81221af9d3676fdbb4f352317ceafd42899aaf5d8a/pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e", size = 5864407, upload-time = "2025-07-03T13:10:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c1/c6c423134229f2a221ee53f838d4be9d82bab86f7e2f8e75e47b6bf6cd77/pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1", size = 7639094, upload-time = "2025-07-03T13:10:21.857Z" }, + { url = "https://files.pythonhosted.org/packages/ba/c9/09e6746630fe6372c67c648ff9deae52a2bc20897d51fa293571977ceb5d/pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805", size = 5973503, upload-time = "2025-07-01T09:14:45.698Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1c/a2a29649c0b1983d3ef57ee87a66487fdeb45132df66ab30dd37f7dbe162/pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8", size = 6642574, upload-time = "2025-07-01T09:14:47.415Z" }, + { url = "https://files.pythonhosted.org/packages/36/de/d5cc31cc4b055b6c6fd990e3e7f0f8aaf36229a2698501bcb0cdf67c7146/pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2", size = 6084060, upload-time = "2025-07-01T09:14:49.636Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ea/502d938cbaeec836ac28a9b730193716f0114c41325db428e6b280513f09/pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b", size = 6721407, upload-time = "2025-07-01T09:14:51.962Z" }, + { url = "https://files.pythonhosted.org/packages/45/9c/9c5e2a73f125f6cbc59cc7087c8f2d649a7ae453f83bd0362ff7c9e2aee2/pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3", size = 6273841, upload-time = "2025-07-01T09:14:54.142Z" }, + { url = "https://files.pythonhosted.org/packages/23/85/397c73524e0cd212067e0c969aa245b01d50183439550d24d9f55781b776/pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51", size = 6978450, upload-time = "2025-07-01T09:14:56.436Z" }, + { url = "https://files.pythonhosted.org/packages/17/d2/622f4547f69cd173955194b78e4d19ca4935a1b0f03a302d655c9f6aae65/pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580", size = 2423055, upload-time = "2025-07-01T09:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/dd/80/a8a2ac21dda2e82480852978416cfacd439a4b490a501a288ecf4fe2532d/pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e", size = 5281110, upload-time = "2025-07-01T09:14:59.79Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/b79754ca790f315918732e18f82a8146d33bcd7f4494380457ea89eb883d/pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d", size = 4689547, upload-time = "2025-07-01T09:15:01.648Z" }, + { url = "https://files.pythonhosted.org/packages/49/20/716b8717d331150cb00f7fdd78169c01e8e0c219732a78b0e59b6bdb2fd6/pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced", size = 5901554, upload-time = "2025-07-03T13:10:27.018Z" }, + { url = "https://files.pythonhosted.org/packages/74/cf/a9f3a2514a65bb071075063a96f0a5cf949c2f2fce683c15ccc83b1c1cab/pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c", size = 7669132, upload-time = "2025-07-03T13:10:33.01Z" }, + { url = "https://files.pythonhosted.org/packages/98/3c/da78805cbdbee9cb43efe8261dd7cc0b4b93f2ac79b676c03159e9db2187/pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8", size = 6005001, upload-time = "2025-07-01T09:15:03.365Z" }, + { url = "https://files.pythonhosted.org/packages/6c/fa/ce044b91faecf30e635321351bba32bab5a7e034c60187fe9698191aef4f/pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59", size = 6668814, upload-time = "2025-07-01T09:15:05.655Z" }, + { url = "https://files.pythonhosted.org/packages/7b/51/90f9291406d09bf93686434f9183aba27b831c10c87746ff49f127ee80cb/pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe", size = 6113124, upload-time = "2025-07-01T09:15:07.358Z" }, + { url = "https://files.pythonhosted.org/packages/cd/5a/6fec59b1dfb619234f7636d4157d11fb4e196caeee220232a8d2ec48488d/pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c", size = 6747186, upload-time = "2025-07-01T09:15:09.317Z" }, + { url = "https://files.pythonhosted.org/packages/49/6b/00187a044f98255225f172de653941e61da37104a9ea60e4f6887717e2b5/pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788", size = 6277546, upload-time = "2025-07-01T09:15:11.311Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5c/6caaba7e261c0d75bab23be79f1d06b5ad2a2ae49f028ccec801b0e853d6/pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31", size = 6985102, upload-time = "2025-07-01T09:15:13.164Z" }, + { url = "https://files.pythonhosted.org/packages/f3/7e/b623008460c09a0cb38263c93b828c666493caee2eb34ff67f778b87e58c/pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e", size = 2424803, upload-time = "2025-07-01T09:15:15.695Z" }, + { url = "https://files.pythonhosted.org/packages/73/f4/04905af42837292ed86cb1b1dabe03dce1edc008ef14c473c5c7e1443c5d/pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12", size = 5278520, upload-time = "2025-07-01T09:15:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/41/b0/33d79e377a336247df6348a54e6d2a2b85d644ca202555e3faa0cf811ecc/pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a", size = 4686116, upload-time = "2025-07-01T09:15:19.423Z" }, + { url = "https://files.pythonhosted.org/packages/49/2d/ed8bc0ab219ae8768f529597d9509d184fe8a6c4741a6864fea334d25f3f/pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632", size = 5864597, upload-time = "2025-07-03T13:10:38.404Z" }, + { url = "https://files.pythonhosted.org/packages/b5/3d/b932bb4225c80b58dfadaca9d42d08d0b7064d2d1791b6a237f87f661834/pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673", size = 7638246, upload-time = "2025-07-03T13:10:44.987Z" }, + { url = "https://files.pythonhosted.org/packages/09/b5/0487044b7c096f1b48f0d7ad416472c02e0e4bf6919541b111efd3cae690/pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027", size = 5973336, upload-time = "2025-07-01T09:15:21.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2d/524f9318f6cbfcc79fbc004801ea6b607ec3f843977652fdee4857a7568b/pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77", size = 6642699, upload-time = "2025-07-01T09:15:23.186Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d2/a9a4f280c6aefedce1e8f615baaa5474e0701d86dd6f1dede66726462bbd/pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874", size = 6083789, upload-time = "2025-07-01T09:15:25.1Z" }, + { url = "https://files.pythonhosted.org/packages/fe/54/86b0cd9dbb683a9d5e960b66c7379e821a19be4ac5810e2e5a715c09a0c0/pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a", size = 6720386, upload-time = "2025-07-01T09:15:27.378Z" }, + { url = "https://files.pythonhosted.org/packages/e7/95/88efcaf384c3588e24259c4203b909cbe3e3c2d887af9e938c2022c9dd48/pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214", size = 6370911, upload-time = "2025-07-01T09:15:29.294Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cc/934e5820850ec5eb107e7b1a72dd278140731c669f396110ebc326f2a503/pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635", size = 7117383, upload-time = "2025-07-01T09:15:31.128Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e9/9c0a616a71da2a5d163aa37405e8aced9a906d574b4a214bede134e731bc/pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6", size = 2511385, upload-time = "2025-07-01T09:15:33.328Z" }, + { url = "https://files.pythonhosted.org/packages/1a/33/c88376898aff369658b225262cd4f2659b13e8178e7534df9e6e1fa289f6/pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae", size = 5281129, upload-time = "2025-07-01T09:15:35.194Z" }, + { url = "https://files.pythonhosted.org/packages/1f/70/d376247fb36f1844b42910911c83a02d5544ebd2a8bad9efcc0f707ea774/pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653", size = 4689580, upload-time = "2025-07-01T09:15:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/eb/1c/537e930496149fbac69efd2fc4329035bbe2e5475b4165439e3be9cb183b/pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6", size = 5902860, upload-time = "2025-07-03T13:10:50.248Z" }, + { url = "https://files.pythonhosted.org/packages/bd/57/80f53264954dcefeebcf9dae6e3eb1daea1b488f0be8b8fef12f79a3eb10/pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36", size = 7670694, upload-time = "2025-07-03T13:10:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/70/ff/4727d3b71a8578b4587d9c276e90efad2d6fe0335fd76742a6da08132e8c/pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b", size = 6005888, upload-time = "2025-07-01T09:15:39.436Z" }, + { url = "https://files.pythonhosted.org/packages/05/ae/716592277934f85d3be51d7256f3636672d7b1abfafdc42cf3f8cbd4b4c8/pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477", size = 6670330, upload-time = "2025-07-01T09:15:41.269Z" }, + { url = "https://files.pythonhosted.org/packages/e7/bb/7fe6cddcc8827b01b1a9766f5fdeb7418680744f9082035bdbabecf1d57f/pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50", size = 6114089, upload-time = "2025-07-01T09:15:43.13Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f5/06bfaa444c8e80f1a8e4bff98da9c83b37b5be3b1deaa43d27a0db37ef84/pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b", size = 6748206, upload-time = "2025-07-01T09:15:44.937Z" }, + { url = "https://files.pythonhosted.org/packages/f0/77/bc6f92a3e8e6e46c0ca78abfffec0037845800ea38c73483760362804c41/pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12", size = 6377370, upload-time = "2025-07-01T09:15:46.673Z" }, + { url = "https://files.pythonhosted.org/packages/4a/82/3a721f7d69dca802befb8af08b7c79ebcab461007ce1c18bd91a5d5896f9/pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db", size = 7121500, upload-time = "2025-07-01T09:15:48.512Z" }, + { url = "https://files.pythonhosted.org/packages/89/c7/5572fa4a3f45740eaab6ae86fcdf7195b55beac1371ac8c619d880cfe948/pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa", size = 2512835, upload-time = "2025-07-01T09:15:50.399Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, + { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, + { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +] + +[[package]] +name = "playwright" +version = "1.55.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet" }, + { name = "pyee" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/3a/c81ff76df266c62e24f19718df9c168f49af93cabdbc4608ae29656a9986/playwright-1.55.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:d7da108a95001e412effca4f7610de79da1637ccdf670b1ae3fdc08b9694c034", size = 40428109, upload-time = "2025-08-28T15:46:20.357Z" }, + { url = "https://files.pythonhosted.org/packages/cf/f5/bdb61553b20e907196a38d864602a9b4a461660c3a111c67a35179b636fa/playwright-1.55.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:8290cf27a5d542e2682ac274da423941f879d07b001f6575a5a3a257b1d4ba1c", size = 38687254, upload-time = "2025-08-28T15:46:23.925Z" }, + { url = "https://files.pythonhosted.org/packages/4a/64/48b2837ef396487807e5ab53c76465747e34c7143fac4a084ef349c293a8/playwright-1.55.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:25b0d6b3fd991c315cca33c802cf617d52980108ab8431e3e1d37b5de755c10e", size = 40428108, upload-time = "2025-08-28T15:46:27.119Z" }, + { url = "https://files.pythonhosted.org/packages/08/33/858312628aa16a6de97839adc2ca28031ebc5391f96b6fb8fdf1fcb15d6c/playwright-1.55.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:c6d4d8f6f8c66c483b0835569c7f0caa03230820af8e500c181c93509c92d831", size = 45905643, upload-time = "2025-08-28T15:46:30.312Z" }, + { url = "https://files.pythonhosted.org/packages/83/83/b8d06a5b5721931aa6d5916b83168e28bd891f38ff56fe92af7bdee9860f/playwright-1.55.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29a0777c4ce1273acf90c87e4ae2fe0130182100d99bcd2ae5bf486093044838", size = 45296647, upload-time = "2025-08-28T15:46:33.221Z" }, + { url = "https://files.pythonhosted.org/packages/06/2e/9db64518aebcb3d6ef6cd6d4d01da741aff912c3f0314dadb61226c6a96a/playwright-1.55.0-py3-none-win32.whl", hash = "sha256:29e6d1558ad9d5b5c19cbec0a72f6a2e35e6353cd9f262e22148685b86759f90", size = 35476046, upload-time = "2025-08-28T15:46:36.184Z" }, + { url = "https://files.pythonhosted.org/packages/46/4f/9ba607fa94bb9cee3d4beb1c7b32c16efbfc9d69d5037fa85d10cafc618b/playwright-1.55.0-py3-none-win_amd64.whl", hash = "sha256:7eb5956473ca1951abb51537e6a0da55257bb2e25fc37c2b75af094a5c93736c", size = 35476048, upload-time = "2025-08-28T15:46:38.867Z" }, + { url = "https://files.pythonhosted.org/packages/21/98/5ca173c8ec906abde26c28e1ecb34887343fd71cc4136261b90036841323/playwright-1.55.0-py3-none-win_arm64.whl", hash = "sha256:012dc89ccdcbd774cdde8aeee14c08e0dd52ddb9135bf10e9db040527386bd76", size = 31225543, upload-time = "2025-08-28T15:46:41.613Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "portalocker" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pywin32", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891, upload-time = "2024-07-13T23:15:34.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/fb/a70a4214956182e0d7a9099ab17d50bfcba1056188e9b14f35b9e2b62a0d/portalocker-2.10.1-py3-none-any.whl", hash = "sha256:53a5984ebc86a025552264b459b46a2086e269b21823cb572f8f28ee759e45bf", size = 18423, upload-time = "2024-07-13T23:15:32.602Z" }, +] + +[[package]] +name = "posthog" +version = "3.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "backoff" }, + { name = "distro" }, + { name = "monotonic" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/a9/ec3bbc23b6f3c23c52e0b5795b1357cca74aa5cfb254213f1e471fef9b4d/posthog-3.25.0.tar.gz", hash = "sha256:9168f3e7a0a5571b6b1065c41b3c171fbc68bfe72c3ac0bfd6e3d2fcdb7df2ca", size = 75968, upload-time = "2025-04-15T21:15:45.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/e2/c158366e621562ef224f132e75c1d1c1fce6b078a19f7d8060451a12d4b9/posthog-3.25.0-py2.py3-none-any.whl", hash = "sha256:85db78c13d1ecb11aed06fad53759c4e8fb3633442c2f3d0336bc0ce8a585d30", size = 89115, upload-time = "2025-04-15T21:15:43.934Z" }, +] + +[[package]] +name = "propcache" +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/bf/df/6d9c1b6ac12b003837dde8a10231a7344512186e87b36e855bef32241942/propcache-0.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:43eedf29202c08550aac1d14e0ee619b0430aaef78f85864c1a892294fbc28cf", size = 77750, upload-time = "2025-10-08T19:47:07.648Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e8/677a0025e8a2acf07d3418a2e7ba529c9c33caf09d3c1f25513023c1db56/propcache-0.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d62cdfcfd89ccb8de04e0eda998535c406bf5e060ffd56be6c586cbcc05b3311", size = 44780, upload-time = "2025-10-08T19:47:08.851Z" }, + { url = "https://files.pythonhosted.org/packages/89/a4/92380f7ca60f99ebae761936bc48a72a639e8a47b29050615eef757cb2a7/propcache-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cae65ad55793da34db5f54e4029b89d3b9b9490d8abe1b4c7ab5d4b8ec7ebf74", size = 46308, upload-time = "2025-10-08T19:47:09.982Z" }, + { url = "https://files.pythonhosted.org/packages/2d/48/c5ac64dee5262044348d1d78a5f85dd1a57464a60d30daee946699963eb3/propcache-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:333ddb9031d2704a301ee3e506dc46b1fe5f294ec198ed6435ad5b6a085facfe", size = 208182, upload-time = "2025-10-08T19:47:11.319Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0c/cd762dd011a9287389a6a3eb43aa30207bde253610cca06824aeabfe9653/propcache-0.4.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fd0858c20f078a32cf55f7e81473d96dcf3b93fd2ccdb3d40fdf54b8573df3af", size = 211215, upload-time = "2025-10-08T19:47:13.146Z" }, + { url = "https://files.pythonhosted.org/packages/30/3e/49861e90233ba36890ae0ca4c660e95df565b2cd15d4a68556ab5865974e/propcache-0.4.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:678ae89ebc632c5c204c794f8dab2837c5f159aeb59e6ed0539500400577298c", size = 218112, upload-time = "2025-10-08T19:47:14.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/8b/544bc867e24e1bd48f3118cecd3b05c694e160a168478fa28770f22fd094/propcache-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d472aeb4fbf9865e0c6d622d7f4d54a4e101a89715d8904282bb5f9a2f476c3f", size = 204442, upload-time = "2025-10-08T19:47:16.277Z" }, + { url = "https://files.pythonhosted.org/packages/50/a6/4282772fd016a76d3e5c0df58380a5ea64900afd836cec2c2f662d1b9bb3/propcache-0.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4d3df5fa7e36b3225954fba85589da77a0fe6a53e3976de39caf04a0db4c36f1", size = 199398, upload-time = "2025-10-08T19:47:17.962Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ec/d8a7cd406ee1ddb705db2139f8a10a8a427100347bd698e7014351c7af09/propcache-0.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ee17f18d2498f2673e432faaa71698032b0127ebf23ae5974eeaf806c279df24", size = 196920, upload-time = "2025-10-08T19:47:19.355Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/f38ab64af3764f431e359f8baf9e0a21013e24329e8b85d2da32e8ed07ca/propcache-0.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:580e97762b950f993ae618e167e7be9256b8353c2dcd8b99ec100eb50f5286aa", size = 203748, upload-time = "2025-10-08T19:47:21.338Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e3/fa846bd70f6534d647886621388f0a265254d30e3ce47e5c8e6e27dbf153/propcache-0.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:501d20b891688eb8e7aa903021f0b72d5a55db40ffaab27edefd1027caaafa61", size = 205877, upload-time = "2025-10-08T19:47:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/e2/39/8163fc6f3133fea7b5f2827e8eba2029a0277ab2c5beee6c1db7b10fc23d/propcache-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a0bd56e5b100aef69bd8562b74b46254e7c8812918d3baa700c8a8009b0af66", size = 199437, upload-time = "2025-10-08T19:47:24.445Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/caa9089970ca49c7c01662bd0eeedfe85494e863e8043565aeb6472ce8fe/propcache-0.4.1-cp313-cp313-win32.whl", hash = "sha256:bcc9aaa5d80322bc2fb24bb7accb4a30f81e90ab8d6ba187aec0744bc302ad81", size = 37586, upload-time = "2025-10-08T19:47:25.736Z" }, + { url = "https://files.pythonhosted.org/packages/f5/ab/f76ec3c3627c883215b5c8080debb4394ef5a7a29be811f786415fc1e6fd/propcache-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:381914df18634f5494334d201e98245c0596067504b9372d8cf93f4bb23e025e", size = 40790, upload-time = "2025-10-08T19:47:26.847Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/e71ae98235f8e2ba5004d8cb19765a74877abf189bc53fc0c80d799e56c3/propcache-0.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:8873eb4460fd55333ea49b7d189749ecf6e55bf85080f11b1c4530ed3034cba1", size = 37158, upload-time = "2025-10-08T19:47:27.961Z" }, + { url = "https://files.pythonhosted.org/packages/83/ce/a31bbdfc24ee0dcbba458c8175ed26089cf109a55bbe7b7640ed2470cfe9/propcache-0.4.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:92d1935ee1f8d7442da9c0c4fa7ac20d07e94064184811b685f5c4fada64553b", size = 81451, upload-time = "2025-10-08T19:47:29.445Z" }, + { url = "https://files.pythonhosted.org/packages/25/9c/442a45a470a68456e710d96cacd3573ef26a1d0a60067e6a7d5e655621ed/propcache-0.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:473c61b39e1460d386479b9b2f337da492042447c9b685f28be4f74d3529e566", size = 46374, upload-time = "2025-10-08T19:47:30.579Z" }, + { url = "https://files.pythonhosted.org/packages/f4/bf/b1d5e21dbc3b2e889ea4327044fb16312a736d97640fb8b6aa3f9c7b3b65/propcache-0.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c0ef0aaafc66fbd87842a3fe3902fd889825646bc21149eafe47be6072725835", size = 48396, upload-time = "2025-10-08T19:47:31.79Z" }, + { url = "https://files.pythonhosted.org/packages/f4/04/5b4c54a103d480e978d3c8a76073502b18db0c4bc17ab91b3cb5092ad949/propcache-0.4.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f95393b4d66bfae908c3ca8d169d5f79cd65636ae15b5e7a4f6e67af675adb0e", size = 275950, upload-time = "2025-10-08T19:47:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c1/86f846827fb969c4b78b0af79bba1d1ea2156492e1b83dea8b8a6ae27395/propcache-0.4.1-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c07fda85708bc48578467e85099645167a955ba093be0a2dcba962195676e859", size = 273856, upload-time = "2025-10-08T19:47:34.906Z" }, + { url = "https://files.pythonhosted.org/packages/36/1d/fc272a63c8d3bbad6878c336c7a7dea15e8f2d23a544bda43205dfa83ada/propcache-0.4.1-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:af223b406d6d000830c6f65f1e6431783fc3f713ba3e6cc8c024d5ee96170a4b", size = 280420, upload-time = "2025-10-08T19:47:36.338Z" }, + { url = "https://files.pythonhosted.org/packages/07/0c/01f2219d39f7e53d52e5173bcb09c976609ba30209912a0680adfb8c593a/propcache-0.4.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a78372c932c90ee474559c5ddfffd718238e8673c340dc21fe45c5b8b54559a0", size = 263254, upload-time = "2025-10-08T19:47:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/2d/18/cd28081658ce597898f0c4d174d4d0f3c5b6d4dc27ffafeef835c95eb359/propcache-0.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:564d9f0d4d9509e1a870c920a89b2fec951b44bf5ba7d537a9e7c1ccec2c18af", size = 261205, upload-time = "2025-10-08T19:47:39.659Z" }, + { url = "https://files.pythonhosted.org/packages/7a/71/1f9e22eb8b8316701c2a19fa1f388c8a3185082607da8e406a803c9b954e/propcache-0.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:17612831fda0138059cc5546f4d12a2aacfb9e47068c06af35c400ba58ba7393", size = 247873, upload-time = "2025-10-08T19:47:41.084Z" }, + { url = "https://files.pythonhosted.org/packages/4a/65/3d4b61f36af2b4eddba9def857959f1016a51066b4f1ce348e0cf7881f58/propcache-0.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:41a89040cb10bd345b3c1a873b2bf36413d48da1def52f268a055f7398514874", size = 262739, upload-time = "2025-10-08T19:47:42.51Z" }, + { url = "https://files.pythonhosted.org/packages/2a/42/26746ab087faa77c1c68079b228810436ccd9a5ce9ac85e2b7307195fd06/propcache-0.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e35b88984e7fa64aacecea39236cee32dd9bd8c55f57ba8a75cf2399553f9bd7", size = 263514, upload-time = "2025-10-08T19:47:43.927Z" }, + { url = "https://files.pythonhosted.org/packages/94/13/630690fe201f5502d2403dd3cfd451ed8858fe3c738ee88d095ad2ff407b/propcache-0.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f8b465489f927b0df505cbe26ffbeed4d6d8a2bbc61ce90eb074ff129ef0ab1", size = 257781, upload-time = "2025-10-08T19:47:45.448Z" }, + { url = "https://files.pythonhosted.org/packages/92/f7/1d4ec5841505f423469efbfc381d64b7b467438cd5a4bbcbb063f3b73d27/propcache-0.4.1-cp313-cp313t-win32.whl", hash = "sha256:2ad890caa1d928c7c2965b48f3a3815c853180831d0e5503d35cf00c472f4717", size = 41396, upload-time = "2025-10-08T19:47:47.202Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/615c30622316496d2cbbc29f5985f7777d3ada70f23370608c1d3e081c1f/propcache-0.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:f7ee0e597f495cf415bcbd3da3caa3bd7e816b74d0d52b8145954c5e6fd3ff37", size = 44897, upload-time = "2025-10-08T19:47:48.336Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/6002e46eccbe0e33dcd4069ef32f7f1c9e243736e07adca37ae8c4830ec3/propcache-0.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:929d7cbe1f01bb7baffb33dc14eb5691c95831450a26354cd210a8155170c93a", size = 39789, upload-time = "2025-10-08T19:47:49.876Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5c/bca52d654a896f831b8256683457ceddd490ec18d9ec50e97dfd8fc726a8/propcache-0.4.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3f7124c9d820ba5548d431afb4632301acf965db49e666aa21c305cbe8c6de12", size = 78152, upload-time = "2025-10-08T19:47:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/65/9b/03b04e7d82a5f54fb16113d839f5ea1ede58a61e90edf515f6577c66fa8f/propcache-0.4.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:c0d4b719b7da33599dfe3b22d3db1ef789210a0597bc650b7cee9c77c2be8c5c", size = 44869, upload-time = "2025-10-08T19:47:52.594Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fa/89a8ef0468d5833a23fff277b143d0573897cf75bd56670a6d28126c7d68/propcache-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9f302f4783709a78240ebc311b793f123328716a60911d667e0c036bc5dcbded", size = 46596, upload-time = "2025-10-08T19:47:54.073Z" }, + { url = "https://files.pythonhosted.org/packages/86/bd/47816020d337f4a746edc42fe8d53669965138f39ee117414c7d7a340cfe/propcache-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c80ee5802e3fb9ea37938e7eecc307fb984837091d5fd262bb37238b1ae97641", size = 206981, upload-time = "2025-10-08T19:47:55.715Z" }, + { url = "https://files.pythonhosted.org/packages/df/f6/c5fa1357cc9748510ee55f37173eb31bfde6d94e98ccd9e6f033f2fc06e1/propcache-0.4.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ed5a841e8bb29a55fb8159ed526b26adc5bdd7e8bd7bf793ce647cb08656cdf4", size = 211490, upload-time = "2025-10-08T19:47:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/80/1e/e5889652a7c4a3846683401a48f0f2e5083ce0ec1a8a5221d8058fbd1adf/propcache-0.4.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:55c72fd6ea2da4c318e74ffdf93c4fe4e926051133657459131a95c846d16d44", size = 215371, upload-time = "2025-10-08T19:47:59.317Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f2/889ad4b2408f72fe1a4f6a19491177b30ea7bf1a0fd5f17050ca08cfc882/propcache-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8326e144341460402713f91df60ade3c999d601e7eb5ff8f6f7862d54de0610d", size = 201424, upload-time = "2025-10-08T19:48:00.67Z" }, + { url = "https://files.pythonhosted.org/packages/27/73/033d63069b57b0812c8bd19f311faebeceb6ba31b8f32b73432d12a0b826/propcache-0.4.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:060b16ae65bc098da7f6d25bf359f1f31f688384858204fe5d652979e0015e5b", size = 197566, upload-time = "2025-10-08T19:48:02.604Z" }, + { url = "https://files.pythonhosted.org/packages/dc/89/ce24f3dc182630b4e07aa6d15f0ff4b14ed4b9955fae95a0b54c58d66c05/propcache-0.4.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:89eb3fa9524f7bec9de6e83cf3faed9d79bffa560672c118a96a171a6f55831e", size = 193130, upload-time = "2025-10-08T19:48:04.499Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/ef0d5fd1a811fb5c609278d0209c9f10c35f20581fcc16f818da959fc5b4/propcache-0.4.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:dee69d7015dc235f526fe80a9c90d65eb0039103fe565776250881731f06349f", size = 202625, upload-time = "2025-10-08T19:48:06.213Z" }, + { url = "https://files.pythonhosted.org/packages/f5/02/98ec20ff5546f68d673df2f7a69e8c0d076b5abd05ca882dc7ee3a83653d/propcache-0.4.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:5558992a00dfd54ccbc64a32726a3357ec93825a418a401f5cc67df0ac5d9e49", size = 204209, upload-time = "2025-10-08T19:48:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/a0/87/492694f76759b15f0467a2a93ab68d32859672b646aa8a04ce4864e7932d/propcache-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c9b822a577f560fbd9554812526831712c1436d2c046cedee4c3796d3543b144", size = 197797, upload-time = "2025-10-08T19:48:09.968Z" }, + { url = "https://files.pythonhosted.org/packages/ee/36/66367de3575db1d2d3f3d177432bd14ee577a39d3f5d1b3d5df8afe3b6e2/propcache-0.4.1-cp314-cp314-win32.whl", hash = "sha256:ab4c29b49d560fe48b696cdcb127dd36e0bc2472548f3bf56cc5cb3da2b2984f", size = 38140, upload-time = "2025-10-08T19:48:11.232Z" }, + { url = "https://files.pythonhosted.org/packages/0c/2a/a758b47de253636e1b8aef181c0b4f4f204bf0dd964914fb2af90a95b49b/propcache-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:5a103c3eb905fcea0ab98be99c3a9a5ab2de60228aa5aceedc614c0281cf6153", size = 41257, upload-time = "2025-10-08T19:48:12.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/5e/63bd5896c3fec12edcbd6f12508d4890d23c265df28c74b175e1ef9f4f3b/propcache-0.4.1-cp314-cp314-win_arm64.whl", hash = "sha256:74c1fb26515153e482e00177a1ad654721bf9207da8a494a0c05e797ad27b992", size = 38097, upload-time = "2025-10-08T19:48:13.923Z" }, + { url = "https://files.pythonhosted.org/packages/99/85/9ff785d787ccf9bbb3f3106f79884a130951436f58392000231b4c737c80/propcache-0.4.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:824e908bce90fb2743bd6b59db36eb4f45cd350a39637c9f73b1c1ea66f5b75f", size = 81455, upload-time = "2025-10-08T19:48:15.16Z" }, + { url = "https://files.pythonhosted.org/packages/90/85/2431c10c8e7ddb1445c1f7c4b54d886e8ad20e3c6307e7218f05922cad67/propcache-0.4.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2b5e7db5328427c57c8e8831abda175421b709672f6cfc3d630c3b7e2146393", size = 46372, upload-time = "2025-10-08T19:48:16.424Z" }, + { url = "https://files.pythonhosted.org/packages/01/20/b0972d902472da9bcb683fa595099911f4d2e86e5683bcc45de60dd05dc3/propcache-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6f6ff873ed40292cd4969ef5310179afd5db59fdf055897e282485043fc80ad0", size = 48411, upload-time = "2025-10-08T19:48:17.577Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e3/7dc89f4f21e8f99bad3d5ddb3a3389afcf9da4ac69e3deb2dcdc96e74169/propcache-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49a2dc67c154db2c1463013594c458881a069fcf98940e61a0569016a583020a", size = 275712, upload-time = "2025-10-08T19:48:18.901Z" }, + { url = "https://files.pythonhosted.org/packages/20/67/89800c8352489b21a8047c773067644e3897f02ecbbd610f4d46b7f08612/propcache-0.4.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:005f08e6a0529984491e37d8dbc3dd86f84bd78a8ceb5fa9a021f4c48d4984be", size = 273557, upload-time = "2025-10-08T19:48:20.762Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a1/b52b055c766a54ce6d9c16d9aca0cad8059acd9637cdf8aa0222f4a026ef/propcache-0.4.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c3310452e0d31390da9035c348633b43d7e7feb2e37be252be6da45abd1abcc", size = 280015, upload-time = "2025-10-08T19:48:22.592Z" }, + { url = "https://files.pythonhosted.org/packages/48/c8/33cee30bd890672c63743049f3c9e4be087e6780906bfc3ec58528be59c1/propcache-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c3c70630930447f9ef1caac7728c8ad1c56bc5015338b20fed0d08ea2480b3a", size = 262880, upload-time = "2025-10-08T19:48:23.947Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/8f08a143b204b418285c88b83d00edbd61afbc2c6415ffafc8905da7038b/propcache-0.4.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8e57061305815dfc910a3634dcf584f08168a8836e6999983569f51a8544cd89", size = 260938, upload-time = "2025-10-08T19:48:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/96e4664c82ca2f31e1c8dff86afb867348979eb78d3cb8546a680287a1e9/propcache-0.4.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:521a463429ef54143092c11a77e04056dd00636f72e8c45b70aaa3140d639726", size = 247641, upload-time = "2025-10-08T19:48:27.207Z" }, + { url = "https://files.pythonhosted.org/packages/18/ed/e7a9cfca28133386ba52278136d42209d3125db08d0a6395f0cba0c0285c/propcache-0.4.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:120c964da3fdc75e3731aa392527136d4ad35868cc556fd09bb6d09172d9a367", size = 262510, upload-time = "2025-10-08T19:48:28.65Z" }, + { url = "https://files.pythonhosted.org/packages/f5/76/16d8bf65e8845dd62b4e2b57444ab81f07f40caa5652b8969b87ddcf2ef6/propcache-0.4.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:d8f353eb14ee3441ee844ade4277d560cdd68288838673273b978e3d6d2c8f36", size = 263161, upload-time = "2025-10-08T19:48:30.133Z" }, + { url = "https://files.pythonhosted.org/packages/e7/70/c99e9edb5d91d5ad8a49fa3c1e8285ba64f1476782fed10ab251ff413ba1/propcache-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ab2943be7c652f09638800905ee1bab2c544e537edb57d527997a24c13dc1455", size = 257393, upload-time = "2025-10-08T19:48:31.567Z" }, + { url = "https://files.pythonhosted.org/packages/08/02/87b25304249a35c0915d236575bc3574a323f60b47939a2262b77632a3ee/propcache-0.4.1-cp314-cp314t-win32.whl", hash = "sha256:05674a162469f31358c30bcaa8883cb7829fa3110bf9c0991fe27d7896c42d85", size = 42546, upload-time = "2025-10-08T19:48:32.872Z" }, + { url = "https://files.pythonhosted.org/packages/cb/ef/3c6ecf8b317aa982f309835e8f96987466123c6e596646d4e6a1dfcd080f/propcache-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:990f6b3e2a27d683cb7602ed6c86f15ee6b43b1194736f9baaeb93d0016633b1", size = 46259, upload-time = "2025-10-08T19:48:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2d/346e946d4951f37eca1e4f55be0f0174c52cd70720f84029b02f296f4a38/propcache-0.4.1-cp314-cp314t-win_arm64.whl", hash = "sha256:ecef2343af4cc68e05131e45024ba34f6095821988a9d0a02aa7c73fcc448aa9", size = 40428, upload-time = "2025-10-08T19:48:35.441Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, +] + +[[package]] +name = "proto-plus" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142, upload-time = "2025-03-10T15:54:38.843Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163, upload-time = "2025-03-10T15:54:37.335Z" }, +] + +[[package]] +name = "protobuf" +version = "6.33.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ff/64a6c8f420818bb873713988ca5492cba3a7946be57e027ac63495157d97/protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954", size = 443463, upload-time = "2025-10-15T20:39:52.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/ee/52b3fa8feb6db4a833dfea4943e175ce645144532e8a90f72571ad85df4e/protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035", size = 425593, upload-time = "2025-10-15T20:39:40.29Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c6/7a465f1825872c55e0341ff4a80198743f73b69ce5d43ab18043699d1d81/protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee", size = 436882, upload-time = "2025-10-15T20:39:42.841Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a9/b6eee662a6951b9c3640e8e452ab3e09f117d99fc10baa32d1581a0d4099/protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455", size = 427521, upload-time = "2025-10-15T20:39:43.803Z" }, + { url = "https://files.pythonhosted.org/packages/10/35/16d31e0f92c6d2f0e77c2a3ba93185130ea13053dd16200a57434c882f2b/protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90", size = 324445, upload-time = "2025-10-15T20:39:44.932Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/2a981a13e35cda8b75b5585aaffae2eb904f8f351bdd3870769692acbd8a/protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298", size = 339159, upload-time = "2025-10-15T20:39:46.186Z" }, + { url = "https://files.pythonhosted.org/packages/21/51/0b1cbad62074439b867b4e04cc09b93f6699d78fd191bed2bbb44562e077/protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef", size = 323172, upload-time = "2025-10-15T20:39:47.465Z" }, + { url = "https://files.pythonhosted.org/packages/07/d1/0a28c21707807c6aacd5dc9c3704b2aa1effbf37adebd8caeaf68b17a636/protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995", size = 170477, upload-time = "2025-10-15T20:39:51.311Z" }, +] + +[[package]] +name = "psutil" +version = "7.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/89/fc/889242351a932d6183eec5df1fc6539b6f36b6a88444f1e63f18668253aa/psutil-7.1.1.tar.gz", hash = "sha256:092b6350145007389c1cfe5716050f02030a05219d90057ea867d18fe8d372fc", size = 487067, upload-time = "2025-10-19T15:43:59.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/30/f97f8fb1f9ecfbeae4b5ca738dcae66ab28323b5cfbc96cb5565f3754056/psutil-7.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:8fa59d7b1f01f0337f12cd10dbd76e4312a4d3c730a4fedcbdd4e5447a8b8460", size = 244221, upload-time = "2025-10-19T15:44:03.145Z" }, + { url = "https://files.pythonhosted.org/packages/7b/98/b8d1f61ebf35f4dbdbaabadf9208282d8adc820562f0257e5e6e79e67bf2/psutil-7.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:2a95104eae85d088891716db676f780c1404fc15d47fde48a46a5d61e8f5ad2c", size = 245660, upload-time = "2025-10-19T15:44:05.657Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4a/b8015d7357fefdfe34bc4a3db48a107bae4bad0b94fb6eb0613f09a08ada/psutil-7.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:98629cd8567acefcc45afe2f4ba1e9290f579eacf490a917967decce4b74ee9b", size = 286963, upload-time = "2025-10-19T15:44:08.877Z" }, + { url = "https://files.pythonhosted.org/packages/3d/3c/b56076bb35303d0733fc47b110a1c9cce081a05ae2e886575a3587c1ee76/psutil-7.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92ebc58030fb054fa0f26c3206ef01c31c29d67aee1367e3483c16665c25c8d2", size = 290118, upload-time = "2025-10-19T15:44:11.897Z" }, + { url = "https://files.pythonhosted.org/packages/dc/af/c13d360c0adc6f6218bf9e2873480393d0f729c8dd0507d171f53061c0d3/psutil-7.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:146a704f224fb2ded2be3da5ac67fc32b9ea90c45b51676f9114a6ac45616967", size = 292587, upload-time = "2025-10-19T15:44:14.67Z" }, + { url = "https://files.pythonhosted.org/packages/90/2d/c933e7071ba60c7862813f2c7108ec4cf8304f1c79660efeefd0de982258/psutil-7.1.1-cp37-abi3-win32.whl", hash = "sha256:295c4025b5cd880f7445e4379e6826f7307e3d488947bf9834e865e7847dc5f7", size = 243772, upload-time = "2025-10-19T15:44:16.938Z" }, + { url = "https://files.pythonhosted.org/packages/be/f3/11fd213fff15427bc2853552138760c720fd65032d99edfb161910d04127/psutil-7.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:9b4f17c5f65e44f69bd3a3406071a47b79df45cf2236d1f717970afcb526bcd3", size = 246936, upload-time = "2025-10-19T15:44:18.663Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8d/8a9a45c8b655851f216c1d44f68e3533dc8d2c752ccd0f61f1aa73be4893/psutil-7.1.1-cp37-abi3-win_arm64.whl", hash = "sha256:5457cf741ca13da54624126cd5d333871b454ab133999a9a103fb097a7d7d21a", size = 243944, upload-time = "2025-10-19T15:44:20.666Z" }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/6c/8767aaa597ba424643dc87348c6f1754dd9f48e80fdc1b9f7ca5c3a7c213/psycopg2-binary-2.9.11.tar.gz", hash = "sha256:b6aed9e096bf63f9e75edf2581aa9a7e7186d97ab5c177aa6c87797cd591236c", size = 379620, upload-time = "2025-10-10T11:14:48.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/ae/8d8266f6dd183ab4d48b95b9674034e1b482a3f8619b33a0d86438694577/psycopg2_binary-2.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0e8480afd62362d0a6a27dd09e4ca2def6fa50ed3a4e7c09165266106b2ffa10", size = 3756452, upload-time = "2025-10-10T11:11:11.583Z" }, + { url = "https://files.pythonhosted.org/packages/4b/34/aa03d327739c1be70e09d01182619aca8ebab5970cd0cfa50dd8b9cec2ac/psycopg2_binary-2.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:763c93ef1df3da6d1a90f86ea7f3f806dc06b21c198fa87c3c25504abec9404a", size = 3863957, upload-time = "2025-10-10T11:11:16.932Z" }, + { url = "https://files.pythonhosted.org/packages/48/89/3fdb5902bdab8868bbedc1c6e6023a4e08112ceac5db97fc2012060e0c9a/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e164359396576a3cc701ba8af4751ae68a07235d7a380c631184a611220d9a4", size = 4410955, upload-time = "2025-10-10T11:11:21.21Z" }, + { url = "https://files.pythonhosted.org/packages/ce/24/e18339c407a13c72b336e0d9013fbbbde77b6fd13e853979019a1269519c/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:d57c9c387660b8893093459738b6abddbb30a7eab058b77b0d0d1c7d521ddfd7", size = 4468007, upload-time = "2025-10-10T11:11:24.831Z" }, + { url = "https://files.pythonhosted.org/packages/91/7e/b8441e831a0f16c159b5381698f9f7f7ed54b77d57bc9c5f99144cc78232/psycopg2_binary-2.9.11-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2c226ef95eb2250974bf6fa7a842082b31f68385c4f3268370e3f3870e7859ee", size = 4165012, upload-time = "2025-10-10T11:11:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/76/a1/2f5841cae4c635a9459fe7aca8ed771336e9383b6429e05c01267b0774cf/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb415404821b6d1c47353ebe9c8645967a5235e6d88f914147e7fd411419e6f", size = 3650985, upload-time = "2025-10-10T11:11:34.975Z" }, + { url = "https://files.pythonhosted.org/packages/84/74/4defcac9d002bca5709951b975173c8c2fa968e1a95dc713f61b3a8d3b6a/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f07c9c4a5093258a03b28fab9b4f151aa376989e7f35f855088234e656ee6a94", size = 3296039, upload-time = "2025-10-10T11:11:40.432Z" }, + { url = "https://files.pythonhosted.org/packages/c8/31/36a1d8e702aa35c38fc117c2b8be3f182613faa25d794b8aeaab948d4c03/psycopg2_binary-2.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cffe9d7697ae7456649617e8bb8d7a45afb71cd13f7ab22af3e5c61f04840908", size = 3345842, upload-time = "2025-10-10T11:11:45.366Z" }, + { url = "https://files.pythonhosted.org/packages/6e/b4/a5375cda5b54cb95ee9b836930fea30ae5a8f14aa97da7821722323d979b/psycopg2_binary-2.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:304fd7b7f97eef30e91b8f7e720b3db75fee010b520e434ea35ed1ff22501d03", size = 2713894, upload-time = "2025-10-10T11:11:48.775Z" }, + { url = "https://files.pythonhosted.org/packages/d8/91/f870a02f51be4a65987b45a7de4c2e1897dd0d01051e2b559a38fa634e3e/psycopg2_binary-2.9.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:be9b840ac0525a283a96b556616f5b4820e0526addb8dcf6525a0fa162730be4", size = 3756603, upload-time = "2025-10-10T11:11:52.213Z" }, + { url = "https://files.pythonhosted.org/packages/27/fa/cae40e06849b6c9a95eb5c04d419942f00d9eaac8d81626107461e268821/psycopg2_binary-2.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f090b7ddd13ca842ebfe301cd587a76a4cf0913b1e429eb92c1be5dbeb1a19bc", size = 3864509, upload-time = "2025-10-10T11:11:56.452Z" }, + { url = "https://files.pythonhosted.org/packages/2d/75/364847b879eb630b3ac8293798e380e441a957c53657995053c5ec39a316/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ab8905b5dcb05bf3fb22e0cf90e10f469563486ffb6a96569e51f897c750a76a", size = 4411159, upload-time = "2025-10-10T11:12:00.49Z" }, + { url = "https://files.pythonhosted.org/packages/6f/a0/567f7ea38b6e1c62aafd58375665a547c00c608a471620c0edc364733e13/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf940cd7e7fec19181fdbc29d76911741153d51cab52e5c21165f3262125685e", size = 4468234, upload-time = "2025-10-10T11:12:04.892Z" }, + { url = "https://files.pythonhosted.org/packages/30/da/4e42788fb811bbbfd7b7f045570c062f49e350e1d1f3df056c3fb5763353/psycopg2_binary-2.9.11-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa0f693d3c68ae925966f0b14b8edda71696608039f4ed61b1fe9ffa468d16db", size = 4166236, upload-time = "2025-10-10T11:12:11.674Z" }, + { url = "https://files.pythonhosted.org/packages/bd/42/c9a21edf0e3daa7825ed04a4a8588686c6c14904344344a039556d78aa58/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ef7a6beb4beaa62f88592ccc65df20328029d721db309cb3250b0aae0fa146c3", size = 3652281, upload-time = "2025-10-10T11:12:17.713Z" }, + { url = "https://files.pythonhosted.org/packages/12/22/dedfbcfa97917982301496b6b5e5e6c5531d1f35dd2b488b08d1ebc52482/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:31b32c457a6025e74d233957cc9736742ac5a6cb196c6b68499f6bb51390bd6a", size = 3298010, upload-time = "2025-10-10T11:12:22.671Z" }, + { url = "https://files.pythonhosted.org/packages/12/9a/0402ded6cbd321da0c0ba7d34dc12b29b14f5764c2fc10750daa38e825fc/psycopg2_binary-2.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b6d93d7c0b61a1dd6197d208ab613eb7dcfdcca0a49c42ceb082257991de9d", size = 3347940, upload-time = "2025-10-10T11:12:26.529Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d2/99b55e85832ccde77b211738ff3925a5d73ad183c0b37bcbbe5a8ff04978/psycopg2_binary-2.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:b33fabeb1fde21180479b2d4667e994de7bbf0eec22832ba5d9b5e4cf65b6c6d", size = 2714147, upload-time = "2025-10-10T11:12:29.535Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a8/a2709681b3ac11b0b1786def10006b8995125ba268c9a54bea6f5ae8bd3e/psycopg2_binary-2.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b8fb3db325435d34235b044b199e56cdf9ff41223a4b9752e8576465170bb38c", size = 3756572, upload-time = "2025-10-10T11:12:32.873Z" }, + { url = "https://files.pythonhosted.org/packages/62/e1/c2b38d256d0dafd32713e9f31982a5b028f4a3651f446be70785f484f472/psycopg2_binary-2.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:366df99e710a2acd90efed3764bb1e28df6c675d33a7fb40df9b7281694432ee", size = 3864529, upload-time = "2025-10-10T11:12:36.791Z" }, + { url = "https://files.pythonhosted.org/packages/11/32/b2ffe8f3853c181e88f0a157c5fb4e383102238d73c52ac6d93a5c8bffe6/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8c55b385daa2f92cb64b12ec4536c66954ac53654c7f15a203578da4e78105c0", size = 4411242, upload-time = "2025-10-10T11:12:42.388Z" }, + { url = "https://files.pythonhosted.org/packages/10/04/6ca7477e6160ae258dc96f67c371157776564679aefd247b66f4661501a2/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c0377174bf1dd416993d16edc15357f6eb17ac998244cca19bc67cdc0e2e5766", size = 4468258, upload-time = "2025-10-10T11:12:48.654Z" }, + { url = "https://files.pythonhosted.org/packages/3c/7e/6a1a38f86412df101435809f225d57c1a021307dd0689f7a5e7fe83588b1/psycopg2_binary-2.9.11-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5c6ff3335ce08c75afaed19e08699e8aacf95d4a260b495a4a8545244fe2ceb3", size = 4166295, upload-time = "2025-10-10T11:12:52.525Z" }, + { url = "https://files.pythonhosted.org/packages/82/56/993b7104cb8345ad7d4516538ccf8f0d0ac640b1ebd8c754a7b024e76878/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ba34475ceb08cccbdd98f6b46916917ae6eeb92b5ae111df10b544c3a4621dc4", size = 3652383, upload-time = "2025-10-10T11:12:56.387Z" }, + { url = "https://files.pythonhosted.org/packages/2d/ac/eaeb6029362fd8d454a27374d84c6866c82c33bfc24587b4face5a8e43ef/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b31e90fdd0f968c2de3b26ab014314fe814225b6c324f770952f7d38abf17e3c", size = 3298168, upload-time = "2025-10-10T11:13:00.403Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8e/b7de019a1f562f72ada81081a12823d3c1590bedc48d7d2559410a2763fe/psycopg2_binary-2.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:04195548662fa544626c8ea0f06561eb6203f1984ba5b4562764fbeb4c3d14b1", size = 3347549, upload-time = "2025-10-10T11:13:03.971Z" }, + { url = "https://files.pythonhosted.org/packages/80/2d/1bb683f64737bbb1f86c82b7359db1eb2be4e2c0c13b947f80efefa7d3e5/psycopg2_binary-2.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:efff12b432179443f54e230fdf60de1f6cc726b6c832db8701227d089310e8aa", size = 2714215, upload-time = "2025-10-10T11:13:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/64/12/93ef0098590cf51d9732b4f139533732565704f45bdc1ffa741b7c95fb54/psycopg2_binary-2.9.11-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:92e3b669236327083a2e33ccfa0d320dd01b9803b3e14dd986a4fc54aa00f4e1", size = 3756567, upload-time = "2025-10-10T11:13:11.885Z" }, + { url = "https://files.pythonhosted.org/packages/7c/a9/9d55c614a891288f15ca4b5209b09f0f01e3124056924e17b81b9fa054cc/psycopg2_binary-2.9.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0deeb03da539fa3577fcb0b3f2554a97f7e5477c246098dbb18091a4a01c16f", size = 3864755, upload-time = "2025-10-10T11:13:17.727Z" }, + { url = "https://files.pythonhosted.org/packages/13/1e/98874ce72fd29cbde93209977b196a2edae03f8490d1bd8158e7f1daf3a0/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b52a3f9bb540a3e4ec0f6ba6d31339727b2950c9772850d6545b7eae0b9d7c5", size = 4411646, upload-time = "2025-10-10T11:13:24.432Z" }, + { url = "https://files.pythonhosted.org/packages/5a/bd/a335ce6645334fb8d758cc358810defca14a1d19ffbc8a10bd38a2328565/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:db4fd476874ccfdbb630a54426964959e58da4c61c9feba73e6094d51303d7d8", size = 4468701, upload-time = "2025-10-10T11:13:29.266Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/c8b4f53f34e295e45709b7568bf9b9407a612ea30387d35eb9fa84f269b4/psycopg2_binary-2.9.11-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47f212c1d3be608a12937cc131bd85502954398aaa1320cb4c14421a0ffccf4c", size = 4166293, upload-time = "2025-10-10T11:13:33.336Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/2a8fe18a4e61cfb3417da67b6318e12691772c0696d79434184a511906dc/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fcf21be3ce5f5659daefd2b3b3b6e4727b028221ddc94e6c1523425579664747", size = 3652650, upload-time = "2025-10-10T11:13:38.181Z" }, + { url = "https://files.pythonhosted.org/packages/76/36/03801461b31b29fe58d228c24388f999fe814dfc302856e0d17f97d7c54d/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:9bd81e64e8de111237737b29d68039b9c813bdf520156af36d26819c9a979e5f", size = 3298663, upload-time = "2025-10-10T11:13:44.878Z" }, + { url = "https://files.pythonhosted.org/packages/67/69/f36abe5f118c1dca6d3726ceae164b9356985805480731ac6712a63f24f0/psycopg2_binary-2.9.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c3cb3a676873d7506825221045bd70e0427c905b9c8ee8d6acd70cfcbd6e576d", size = 3347643, upload-time = "2025-10-10T11:13:53.499Z" }, + { url = "https://files.pythonhosted.org/packages/e1/36/9c0c326fe3a4227953dfb29f5d0c8ae3b8eb8c1cd2967aa569f50cb3c61f/psycopg2_binary-2.9.11-cp314-cp314-win_amd64.whl", hash = "sha256:4012c9c954dfaccd28f94e84ab9f94e12df76b4afb22331b1f0d3154893a6316", size = 2803913, upload-time = "2025-10-10T11:13:57.058Z" }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322, upload-time = "2024-09-10T22:41:42.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135, upload-time = "2024-09-11T16:00:36.122Z" }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892, upload-time = "2025-03-28T02:41:22.17Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, +] + +[[package]] +name = "pycparser" +version = "2.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, +] + +[[package]] +name = "pydantic" +version = "2.10.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b7/ae/d5220c5c52b158b1de7ca89fc5edb72f304a70a4c540c84c8844bf4008de/pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236", size = 761681, upload-time = "2025-01-24T01:42:12.693Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/3c/8cc1cc84deffa6e25d2d0c688ebb80635dfdbf1dbea3e30c541c8cf4d860/pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584", size = 431696, upload-time = "2025-01-24T01:42:10.371Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443, upload-time = "2024-12-18T11:31:54.917Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421, upload-time = "2024-12-18T11:27:55.409Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998, upload-time = "2024-12-18T11:27:57.252Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167, upload-time = "2024-12-18T11:27:59.146Z" }, + { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071, upload-time = "2024-12-18T11:28:02.625Z" }, + { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244, upload-time = "2024-12-18T11:28:04.442Z" }, + { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470, upload-time = "2024-12-18T11:28:07.679Z" }, + { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291, upload-time = "2024-12-18T11:28:10.297Z" }, + { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613, upload-time = "2024-12-18T11:28:13.362Z" }, + { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355, upload-time = "2024-12-18T11:28:16.587Z" }, + { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661, upload-time = "2024-12-18T11:28:18.407Z" }, + { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261, upload-time = "2024-12-18T11:28:21.471Z" }, + { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361, upload-time = "2024-12-18T11:28:23.53Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484, upload-time = "2024-12-18T11:28:25.391Z" }, + { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102, upload-time = "2024-12-18T11:28:28.593Z" }, + { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127, upload-time = "2024-12-18T11:28:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340, upload-time = "2024-12-18T11:28:32.521Z" }, + { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900, upload-time = "2024-12-18T11:28:34.507Z" }, + { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177, upload-time = "2024-12-18T11:28:36.488Z" }, + { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046, upload-time = "2024-12-18T11:28:39.409Z" }, + { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386, upload-time = "2024-12-18T11:28:41.221Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060, upload-time = "2024-12-18T11:28:44.709Z" }, + { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870, upload-time = "2024-12-18T11:28:46.839Z" }, + { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822, upload-time = "2024-12-18T11:28:48.896Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364, upload-time = "2024-12-18T11:28:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303, upload-time = "2024-12-18T11:28:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064, upload-time = "2024-12-18T11:28:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046, upload-time = "2024-12-18T11:28:58.107Z" }, + { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092, upload-time = "2024-12-18T11:29:01.335Z" }, + { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709, upload-time = "2024-12-18T11:29:03.193Z" }, + { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273, upload-time = "2024-12-18T11:29:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027, upload-time = "2024-12-18T11:29:07.294Z" }, + { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888, upload-time = "2024-12-18T11:29:09.249Z" }, + { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738, upload-time = "2024-12-18T11:29:11.23Z" }, + { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138, upload-time = "2024-12-18T11:29:16.396Z" }, + { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025, upload-time = "2024-12-18T11:29:20.25Z" }, + { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633, upload-time = "2024-12-18T11:29:23.877Z" }, + { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404, upload-time = "2024-12-18T11:29:25.872Z" }, + { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130, upload-time = "2024-12-18T11:29:29.252Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946, upload-time = "2024-12-18T11:29:31.338Z" }, + { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387, upload-time = "2024-12-18T11:29:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453, upload-time = "2024-12-18T11:29:35.533Z" }, + { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186, upload-time = "2024-12-18T11:29:37.649Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/20/c5/dbbc27b814c71676593d1c3f718e6cd7d4f00652cefa24b75f7aa3efb25e/pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180", size = 188394, upload-time = "2025-09-24T14:19:11.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/d6/887a1ff844e64aa823fb4905978d882a633cfe295c32eacad582b78a7d8b/pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c", size = 48608, upload-time = "2025-09-24T14:19:10.015Z" }, +] + +[[package]] +name = "pydub" +version = "0.25.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/9a/e6bca0eed82db26562c73b5076539a4a08d3cffd19c3cc5913a3e61145fd/pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f", size = 38326, upload-time = "2021-03-10T02:09:54.659Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a6/53/d78dc063216e62fc55f6b2eebb447f6a4b0a59f55c8406376f76bf959b08/pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6", size = 32327, upload-time = "2021-03-10T02:09:53.503Z" }, +] + +[[package]] +name = "pyee" +version = "13.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pyobjc" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-addressbook" }, + { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-applescriptkit" }, + { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-applicationservices" }, + { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-arkit", marker = "platform_release >= '25.0'" }, + { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-automator" }, + { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, + { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-carbon" }, + { name = "pyobjc-framework-cfnetwork" }, + { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-compositorservices", marker = "platform_release >= '25.0'" }, + { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coreaudiokit" }, + { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-coremidi" }, + { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, + { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-discrecording" }, + { name = "pyobjc-framework-discrecordingui" }, + { name = "pyobjc-framework-diskarbitration" }, + { name = "pyobjc-framework-dvdplayback" }, + { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-exceptionhandling" }, + { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4'" }, + { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-gamesave", marker = "platform_release >= '25.0'" }, + { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-installerplugins" }, + { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-iobluetooth" }, + { name = "pyobjc-framework-iobluetoothui" }, + { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-latentsemanticmapping" }, + { name = "pyobjc-framework-launchservices" }, + { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, + { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-osakit" }, + { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-preferencepanes" }, + { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, + { name = "pyobjc-framework-screensaver" }, + { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-searchkit" }, + { name = "pyobjc-framework-security" }, + { name = "pyobjc-framework-securityfoundation" }, + { name = "pyobjc-framework-securityinterface" }, + { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4'" }, + { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-syncservices" }, + { name = "pyobjc-framework-systemconfiguration" }, + { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-webkit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/0f/0b21447c9461905022aab2f19626e94a0b00eee9c6d3593a5ab425f7a42e/pyobjc-12.0.tar.gz", hash = "sha256:ce6b7c68889722248250d1b4daac28272100634e3a9826affdbd6f36a0dc52b2", size = 11236, upload-time = "2025-10-21T08:25:05.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/36/f5335452694fb4bc0dd69affe516886abde64ad43ed88d9b104d822a29de/pyobjc-12.0-py3-none-any.whl", hash = "sha256:cc0004c8e615d4b99f4910804477b322d951d472d5ee20bfef8f390ea734d038", size = 4204, upload-time = "2025-10-21T07:49:12.453Z" }, +] + +[[package]] +name = "pyobjc-core" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/dc/6d63019133e39e2b299dfbab786e64997fff0f145c45a417e1dd51faaf3f/pyobjc_core-12.0.tar.gz", hash = "sha256:7e05c805a776149a937b61b892a0459895d32d9002bedc95ce2be31ef1e37a29", size = 991669, upload-time = "2025-10-21T08:26:07.496Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/c1/c50e312d32644429d8a9bb3a342aeeb772fba85f9573e7681ca458124a8f/pyobjc_core-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dd4962aceb0f9a0ee510e11ced449323db85e42664ac9ade53ad1cc2394dc248", size = 673921, upload-time = "2025-10-21T07:50:09.974Z" }, + { url = "https://files.pythonhosted.org/packages/38/95/1acf3be6a8ae457a26e8ff6e08aeb71af49bfc79303b331067c058d448a4/pyobjc_core-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1675dbb700b6bb6e3f3c9ce3f5401947e0193e16085eeb70e9160c6c6fc1ace5", size = 681179, upload-time = "2025-10-21T07:50:40.094Z" }, + { url = "https://files.pythonhosted.org/packages/88/17/6c247bf9d8de2813f6015671f242333534797e81bdac9e85516fb57dfb00/pyobjc_core-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c44b76d8306a130c9eb0cb79d86fd6675c8ba3e5b458e78095d271a10cd38b6a", size = 679700, upload-time = "2025-10-21T07:51:09.518Z" }, + { url = "https://files.pythonhosted.org/packages/08/a3/1b26c438c78821e5a82b9c02f7b19a86097aeb2c51132d06e159acc22dc2/pyobjc_core-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5c617551e0ab860c49229fcec0135a5cde702485f22254ddc17205eb24b7fc55", size = 721370, upload-time = "2025-10-21T07:51:55.981Z" }, + { url = "https://files.pythonhosted.org/packages/35/b1/6df7d4b0d9f0088855a59f6af59230d1191f78fa84ca68851723272f1916/pyobjc_core-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c2709ff43ac5c2e9e2c574ae515d3aa0e470345847a4d96c5d4a04b1b86e966d", size = 672302, upload-time = "2025-10-21T07:52:39.445Z" }, + { url = "https://files.pythonhosted.org/packages/f8/10/3a029797c0a22c730ee0d0149ac34ab27afdf51667f96aa23a8ebe7dc3c9/pyobjc_core-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:eb6b987e53291e7cafd8f71a80a2dd44d7afec4202a143a3e47b75cb9cdb5716", size = 713255, upload-time = "2025-10-21T07:53:25.478Z" }, +] + +[[package]] +name = "pyobjc-framework-accessibility" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/87/77/28cf2885e6964932773456114ba1012e2a5c60f31582a2dc4980aa6018a9/pyobjc_framework_accessibility-12.0.tar.gz", hash = "sha256:a7794887330d4e50d41af72633d08aa41a9e946a80c49b4ede4a2f7936751c46", size = 30002, upload-time = "2025-10-21T08:26:11.274Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/c6/dec3b6cf566ca01c5ba7c812dafa48b1c29bcfb19960210e53892e8ff4c0/pyobjc_framework_accessibility-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:712200ae59303ea76a00ecb4ecb4ee59c97e4d1fc66fe1555d053f3b320f3915", size = 11270, upload-time = "2025-10-21T07:53:30.336Z" }, + { url = "https://files.pythonhosted.org/packages/a1/fd/d24ad39478e9570d9af493d34732ed6122f87a0d2ce0c946409d1cf40207/pyobjc_framework_accessibility-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10bf22840844654ff67e398b89458dbd7273257aaf638880a2067fb523b51704", size = 11301, upload-time = "2025-10-21T07:53:32.383Z" }, + { url = "https://files.pythonhosted.org/packages/2d/12/2548c021c31e9931a026ace2f85ab9e8c2781f8916e5773398e198a53bc8/pyobjc_framework_accessibility-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3a7aa16ff51111d19992dbe971a52f9cd21afacadd18c4912d266405d834a6a1", size = 11320, upload-time = "2025-10-21T07:53:34.133Z" }, + { url = "https://files.pythonhosted.org/packages/45/a1/3c28c9235c808cb29964178d71859bfcfbc5446c78cf1d8ae45c72a4e3e6/pyobjc_framework_accessibility-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93a7bbfad141ef389935cb84cc2ce3a564b88828440167131b8e15b4407fccd0", size = 11489, upload-time = "2025-10-21T07:53:36.865Z" }, + { url = "https://files.pythonhosted.org/packages/da/3f/bf0f22de28f179a11c465b5aa41d2e8fd5013819825bf2256529808d39b7/pyobjc_framework_accessibility-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7e304153f4c031ed6a3c573d7234eaf95684420f1341e305ebd62e5822b531b1", size = 11380, upload-time = "2025-10-21T07:53:38.657Z" }, + { url = "https://files.pythonhosted.org/packages/40/40/65d2a26235363c2602b88279b105a8b368a4de32c71863ae9497304275d5/pyobjc_framework_accessibility-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:61b82d8f05c61f4a052066460caffd96516b964516c4bc487c6143c6642f36a4", size = 11567, upload-time = "2025-10-21T07:53:40.389Z" }, +] + +[[package]] +name = "pyobjc-framework-accounts" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e3/77/da53be3992e793a857fb07fe3dfc3a595b9c2365f00451578d2843413d30/pyobjc_framework_accounts-12.0.tar.gz", hash = "sha256:48fa0d270208655fa47b89452fa3ef5eadadf61ecf5935b83f22bcb3c28feabe", size = 15288, upload-time = "2025-10-21T08:26:13.567Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/b3/e18aa7763b1de9a116862a022f21d35fbedeb5e8d4aff9633446d3088bef/pyobjc_framework_accounts-12.0-py2.py3-none-any.whl", hash = "sha256:9a12dcb35c4367ab846abcd3a529778ba527155b31249380a8eb360baacdcb05", size = 5116, upload-time = "2025-10-21T07:53:41.836Z" }, +] + +[[package]] +name = "pyobjc-framework-addressbook" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b4/9e/fed3073b5e712d3ed14d27410f03e84c1ea164c560ac7b597b1e6fc8dea8/pyobjc_framework_addressbook-12.0.tar.gz", hash = "sha256:1004b7d8e610748c9ce61aeab766319c2632d1e314838e95eb10f0dd6a64f3d8", size = 44733, upload-time = "2025-10-21T08:26:17.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/15/e0b1ed13a66676152490f220bd325894703348a2dd0e9e349072e8be621e/pyobjc_framework_addressbook-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:773908f0c7c126079ca9afff6679487a62c385511250d43d97508a1f4213621a", size = 12887, upload-time = "2025-10-21T07:53:46.15Z" }, + { url = "https://files.pythonhosted.org/packages/90/cb/4e6b1871e3e1159854c3f23aeded18bfb4b3ba536296bdbd2218db27eb44/pyobjc_framework_addressbook-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc1eef81979b6c64b68e33a96cecd07b9999e0f5c9e0bccb4f48702f2caecfe1", size = 12899, upload-time = "2025-10-21T07:53:48.047Z" }, + { url = "https://files.pythonhosted.org/packages/11/f7/e794035122e8ec21f2411483145a966ef1716cfba6001b1d657325b6cdb4/pyobjc_framework_addressbook-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:05ade2fada2ba7601799a2243496fefdb9e708157e4676c07f29b741c78edc5b", size = 12919, upload-time = "2025-10-21T07:53:50.289Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ac/242cf2b0d292b28ff00ebb8f46cfd6882c0dc4a72662ad22243eed80eda0/pyobjc_framework_addressbook-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:87ed2a5004ff58778b999e7006ba325659d3e74ba6cbe97f73108ce65240b1fb", size = 13074, upload-time = "2025-10-21T07:53:52.583Z" }, + { url = "https://files.pythonhosted.org/packages/76/d8/6d23d431d87384f55b85fe47f8c8deda9f025c9ff2c6ac46325ddbc0af7e/pyobjc_framework_addressbook-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:10c8274a4f369c27f608ed4e36343dc5a37e11f53adfb4069124e290e1af3bba", size = 12977, upload-time = "2025-10-21T07:53:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ee/45/dec0a83a532dc345bd013f04c4d8e0aa117aa1e2c3fbc79891f8057d41f9/pyobjc_framework_addressbook-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c541a39c51988ed1e29043a6bd23ac31e37edf2fe9b41bc0b09bf1cbb4d4f632", size = 13142, upload-time = "2025-10-21T07:53:56.314Z" }, +] + +[[package]] +name = "pyobjc-framework-adservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/63/98e08ce5ba933b104fe73126c1050fc2a4c02ebd654f1ecba272d98892d2/pyobjc_framework_adservices-12.0.tar.gz", hash = "sha256:e58ec0c617f9967d1c1b717fb291ce675555f7ece0b3999d2e8b74d2a49c161e", size = 11834, upload-time = "2025-10-21T08:26:19.448Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/26/ecad8d077c3ce9662fdd57c6c0d1d6ba89b8bd96bcfe4ed28f6c214365f8/pyobjc_framework_adservices-12.0-py2.py3-none-any.whl", hash = "sha256:bf6f6992a00295e936a0cde486f20cf0747b0341d317ead3a353c6c7d327a2e2", size = 3505, upload-time = "2025-10-21T07:53:57.987Z" }, +] + +[[package]] +name = "pyobjc-framework-adsupport" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b0/e2/0deac6d431ba4b319784b8b25e6bd060385556d50ff1b76aab7b43d54972/pyobjc_framework_adsupport-12.0.tar.gz", hash = "sha256:accaaa66739260b5420aa085cfb1dd1fc4b0b52c59076124b9355bd60d2c129c", size = 11714, upload-time = "2025-10-21T08:26:21.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/bb/82529e38c1f83f08a4f84241e2935ad3c545142a8e7d65d9c5461e6ca56e/pyobjc_framework_adsupport-12.0-py2.py3-none-any.whl", hash = "sha256:649fb4114cf1f16bb9c402c360a39eb0ea84e72e49cd6db5451a2806bbc05b24", size = 3412, upload-time = "2025-10-21T07:53:59.452Z" }, +] + +[[package]] +name = "pyobjc-framework-applescriptkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/ee/9f861171c5dbc1f132e884415e573038372fb1af83c1d23fdaeae20ab4e3/pyobjc_framework_applescriptkit-12.0.tar.gz", hash = "sha256:69f57f2f6dd72bdb83f69e33839438caf804302fb177e00136cd49a172e6cc32", size = 11504, upload-time = "2025-10-21T08:26:22.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/84/595a8acb19958de210f04c5d79bff30337d04ca00c20374db4acbfe5c83d/pyobjc_framework_applescriptkit-12.0-py2.py3-none-any.whl", hash = "sha256:940e10bc281a0155a01f817275b11c6819ae773891847c8c90403d27aa6efb5d", size = 4363, upload-time = "2025-10-21T07:54:00.974Z" }, +] + +[[package]] +name = "pyobjc-framework-applescriptobjc" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2a/81/28f123566793ff9037a218a393272a569020ebd228f343dccb6920855355/pyobjc_framework_applescriptobjc-12.0.tar.gz", hash = "sha256:5d89b060fa960bc34b5a505cd5fbbd3625c8035d7246ff0315a00acb205e8a92", size = 11624, upload-time = "2025-10-21T08:26:24.955Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/e7/f53cb5ade63db949ecde23bdcc20867453f24d6faf29b9fa2a2276ab252c/pyobjc_framework_applescriptobjc-12.0-py2.py3-none-any.whl", hash = "sha256:6b4926a29ea2cefea482ff28152dda0e05f2f8ec6d9f84d97a6d19bb872f824b", size = 4461, upload-time = "2025-10-21T07:54:02.723Z" }, +] + +[[package]] +name = "pyobjc-framework-applicationservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/79/0b7a00bcc7561c816281382c933a46aa7a90acca48b942054b7d32d0caf7/pyobjc_framework_applicationservices-12.0.tar.gz", hash = "sha256:eabbf6c57573158714aa656e5d0112330a87692db336aae7e94e216db89e93be", size = 103595, upload-time = "2025-10-21T08:26:32.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/ba/62e7bfce26b1f742a4b6f204a77d807e14766ceb3c6b9f702be6de3f9b38/pyobjc_framework_applicationservices-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5d9684f53b42d534fd67a23a9958c53bf6c738e7b478fa3a87263865a013f287", size = 32799, upload-time = "2025-10-21T07:54:08.913Z" }, + { url = "https://files.pythonhosted.org/packages/74/3a/3db8a9bdd895781d67eeb096064944b36e0fb48caded27b62ec499b78a2b/pyobjc_framework_applicationservices-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e1a89cd9da992a07497d93931edc6469cc53c39dc0ab47b62eaa4d10204c37c6", size = 32850, upload-time = "2025-10-21T07:54:12.003Z" }, + { url = "https://files.pythonhosted.org/packages/9b/cf/ae603c46217c04ec7598c62a2d46fa9b6ab66e127148bff1f352b850fc72/pyobjc_framework_applicationservices-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9ff39c0301f2430253fbfea114afb00594426e0b66a1bec1c28cd60f75d02005", size = 32871, upload-time = "2025-10-21T07:54:15.33Z" }, + { url = "https://files.pythonhosted.org/packages/c6/79/a578c8b1aa8634c2c9f8bbd66a3cdc385013a4cd9558741a4da26c040e51/pyobjc_framework_applicationservices-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ecd7651ab330790722f3590465392dbab3d76be0370ff7e015584053d571e218", size = 33132, upload-time = "2025-10-21T07:54:18.388Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8b/336788981a3c1aa00e75d021a5ed00e453587da1eee0d55bb8b674f2b623/pyobjc_framework_applicationservices-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b153a0b8e915751ca50651be6f9fe002ef7536677f5c37a4dff0f3fd98e5b16a", size = 33007, upload-time = "2025-10-21T07:54:21.971Z" }, + { url = "https://files.pythonhosted.org/packages/a8/50/0e300544e8204d02b4a0477fa157e904921c98b15f67e19b4a49a80f02c9/pyobjc_framework_applicationservices-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:56f8fabdc3972fc9a97630b24c31d8b852502c3273071ab3d3b467cc5e7c6431", size = 33250, upload-time = "2025-10-21T07:54:25.395Z" }, +] + +[[package]] +name = "pyobjc-framework-apptrackingtransparency" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/bb/7cde677be892d94ca07b82612704861899710865e650530c5a0fed91fbea/pyobjc_framework_apptrackingtransparency-12.0.tar.gz", hash = "sha256:22bd689ab7a6b457ece8bf86cad615af10c2f36203ea4307273f74e4e372cdf4", size = 12468, upload-time = "2025-10-21T08:26:34.845Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/42/1fd41fd755fb686f2842a51610351904e1414448fe306fa3ff2d9a72e8dd/pyobjc_framework_apptrackingtransparency-12.0-py2.py3-none-any.whl", hash = "sha256:543d9eb6ce6397930b8eb6e7162e6592f708f251f2fd6e9307bfa965daf10f7d", size = 3891, upload-time = "2025-10-21T07:54:26.96Z" }, +] + +[[package]] +name = "pyobjc-framework-arkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/32/edd3198e33e9ad0e5d47cb228c1346a05a6523d242af1f9dd74ec2ef3c8b/pyobjc_framework_arkit-12.0.tar.gz", hash = "sha256:29c34f5db22f084cf1ae285562a5ad6522f9166d725eb55df987021f8d02e257", size = 35830, upload-time = "2025-10-21T08:26:37.852Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/23/43d3032baebebb2d35055c56a3c42f31a68fb84dc80443e565644ac213c0/pyobjc_framework_arkit-12.0-py2.py3-none-any.whl", hash = "sha256:90997c4e205bb2023886f59de635d1d9ded139d0add8d9941c8ebb69d5a92284", size = 8310, upload-time = "2025-10-21T07:54:28.73Z" }, +] + +[[package]] +name = "pyobjc-framework-audiovideobridging" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/16/92f2ecb7ad7329ff25b44b7cc1d7bd6dbf56bc4511c99cd1b157d4f4941f/pyobjc_framework_audiovideobridging-12.0.tar.gz", hash = "sha256:b38b564b4b2f5edbba8bfde8e0c26eef3a7a654faf0ad0a1b2a1ea6219371772", size = 38916, upload-time = "2025-10-21T08:26:41.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/78/172a079cc7377f9084a4b8d869e48b4ae7a9891a1b195e66dc56ecc9b9ee/pyobjc_framework_audiovideobridging-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:472917360aee1c74012f2ff682fdfe6fb52c5bcf3214bf46121c13085ee82edd", size = 11047, upload-time = "2025-10-21T07:54:32.648Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/2df9d98c4e50123bb7f5f883406527049975b7415b0e4401bb90812e004f/pyobjc_framework_audiovideobridging-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9154664ff6ab0a2f13d5142eb3fb16dae607f46b9dc91bab3712080db4f29ad9", size = 11056, upload-time = "2025-10-21T07:54:34.643Z" }, + { url = "https://files.pythonhosted.org/packages/7e/97/0ffc62736fd0326ce2c9cbff469dea3fc8d00f9a994b533476fdef8c1fc9/pyobjc_framework_audiovideobridging-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:792a70c1111480a75732cbbc7004c071f2a3d6aedddff8d2af22727fb235a519", size = 11078, upload-time = "2025-10-21T07:54:36.374Z" }, + { url = "https://files.pythonhosted.org/packages/23/96/237a77a7a09f4a1bd6b52f84aaa628e3adfd62e31ed299ff6868f97e5f55/pyobjc_framework_audiovideobridging-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ff02fefd09bcb3636bb3891bec850ed60940c06e57ee6463ac48df27ada6ecd1", size = 11250, upload-time = "2025-10-21T07:54:38.121Z" }, + { url = "https://files.pythonhosted.org/packages/df/9b/fd15d1586e6b6df028eeda202629093d6c60e0d7327986381c4e9b31cb08/pyobjc_framework_audiovideobridging-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d847c0982df26014326e27aeecdbec803d48663a3bdbeb0b2492820bdb43b789", size = 11138, upload-time = "2025-10-21T07:54:40.273Z" }, + { url = "https://files.pythonhosted.org/packages/77/92/ecdbf0e1c3455884a01744982533605b0304a7d33c669642bce2301b237c/pyobjc_framework_audiovideobridging-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4f30bb5aa605a5330fde3ab51f238130fb3cfb6227fc3b466bbdf8388b33bcc4", size = 11311, upload-time = "2025-10-21T07:54:42.061Z" }, +] + +[[package]] +name = "pyobjc-framework-authenticationservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/09/2e51e8e72a72536c3721124bdd6ac93f88ec28ad352a35437536ec08c70f/pyobjc_framework_authenticationservices-12.0.tar.gz", hash = "sha256:6dbc94140584d439d5106fd3b64db97c3681ff27c9b3793a6e7885df9974af16", size = 58917, upload-time = "2025-10-21T08:26:46.164Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/78/87aceec2f0586cfbf6560916cdbe954dc419135f335dda1ec7194d24c3cb/pyobjc_framework_authenticationservices-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:24bc6e5855a2029a9d23cd8b209d574fa55d3cadcab5c91c357c78fea90a31eb", size = 20632, upload-time = "2025-10-21T07:54:47.099Z" }, + { url = "https://files.pythonhosted.org/packages/64/38/f552ee4019ef752156d53f0ba56e167175976ff2e2bea6c48284dbcc96e5/pyobjc_framework_authenticationservices-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c2c534cf2583aa811477ab1bb69a52137bd076a704e563922eee5e3d6b906d6", size = 20734, upload-time = "2025-10-21T07:54:49.778Z" }, + { url = "https://files.pythonhosted.org/packages/25/cf/6c5ab3861d2ea4e65f760955d57f8c2f2b2342480ea4d58ea395ad77232b/pyobjc_framework_authenticationservices-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:113e6cf5db0f04194e5fa290f03c32390d667e330b524cdf62a47df1b5974917", size = 20744, upload-time = "2025-10-21T07:54:52.482Z" }, + { url = "https://files.pythonhosted.org/packages/be/e6/2958b9cc06808c2e129bb9e13184818227c7b42b7dcbcde41f7d66153e80/pyobjc_framework_authenticationservices-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ee7a913fa66a7adedfeadb6a663096945119ce0b8c237ed2db3b328b083d1e91", size = 20991, upload-time = "2025-10-21T07:54:55.105Z" }, + { url = "https://files.pythonhosted.org/packages/83/4a/31cd3c2bc7538f81d047e64fed7e7034a35d8227d6633bc341a18c5cd9e5/pyobjc_framework_authenticationservices-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0e31113dc12946dfd15aa5d0f2933aa077b69b0510f213fd6517192e27a09cb9", size = 20750, upload-time = "2025-10-21T07:54:57.336Z" }, + { url = "https://files.pythonhosted.org/packages/2a/1c/b124c0d9aec42bd770e9803743e52228202c709f7183265d6996db0cec5b/pyobjc_framework_authenticationservices-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:46cbdbfa8ad581cf1d3e0da9e1256a92b663aab42f3a89a9acf2fd8fe4f99e94", size = 21027, upload-time = "2025-10-21T07:54:59.662Z" }, +] + +[[package]] +name = "pyobjc-framework-automaticassessmentconfiguration" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/74/e1bb0cfd93cfbdfec173c141d2bbb619e9b500551209ba9d8da81e896665/pyobjc_framework_automaticassessmentconfiguration-12.0.tar.gz", hash = "sha256:8922e5366d2cd6e09f8366e85afe012f9b7fa81d192f98674daa55f098de3f1e", size = 22045, upload-time = "2025-10-21T08:26:48.589Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/02/8c5b940ec9b99e6b0063fed93348139c58843fdb94dcdadad4fd48fb5b70/pyobjc_framework_automaticassessmentconfiguration-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:81bcf67f109557600ac461c14c0ee0f0a87d3c3b8bc7f9a7b44eec6540b97164", size = 9278, upload-time = "2025-10-21T07:55:04.609Z" }, + { url = "https://files.pythonhosted.org/packages/d1/38/d741db0a685cf3e4b2267f494d8af1966344f3813816a9e61666e94d8091/pyobjc_framework_automaticassessmentconfiguration-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef0cb8569234770f71d8d62e393fa5fa69155fd47b81cfd1e4e803585cb5f389", size = 9296, upload-time = "2025-10-21T07:55:06.574Z" }, + { url = "https://files.pythonhosted.org/packages/aa/02/b1afb728f6369b18824f139d89ac3b500beddd3f93e3993da9e9b12943fb/pyobjc_framework_automaticassessmentconfiguration-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c44bf1d4980b35e16858a93a357df73cd77b972096e4675b57058f4d2095b82d", size = 9309, upload-time = "2025-10-21T07:55:08.337Z" }, + { url = "https://files.pythonhosted.org/packages/50/2a/e992f84082e9daa857f771b85fca96cdc0e7edad93511228e7514bf24368/pyobjc_framework_automaticassessmentconfiguration-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:818fef0fb48d122676422f9b639573eefd7fb05814ec28ca0f7de5e669895bbb", size = 9459, upload-time = "2025-10-21T07:55:10.043Z" }, + { url = "https://files.pythonhosted.org/packages/17/24/10d1119a6fdbf933bf9128baa8dee30b7c30aa3b2c212c3a58ace111dd15/pyobjc_framework_automaticassessmentconfiguration-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:af22320b893869ecc2371af831de483bc7d0f885c7a032456c9ea90f95d57911", size = 9350, upload-time = "2025-10-21T07:55:11.756Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a9/c4582418bbd114c4fcb5c86d8c126878ee34dfc05ff368a7991562b40330/pyobjc_framework_automaticassessmentconfiguration-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:12d8c2f3ab3d8790ab1d84deee6d5c21eff7808a876e31995d4474e76703fcb0", size = 9504, upload-time = "2025-10-21T07:55:13.409Z" }, +] + +[[package]] +name = "pyobjc-framework-automator" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/d3/17178d3c6fde3f95718f9832a799d2328e59ba5158d1434fe2767c957187/pyobjc_framework_automator-12.0.tar.gz", hash = "sha256:7c2f0236b2a474a2d411835419e8f140e0f563be299f770fe8762f96d254443d", size = 186429, upload-time = "2025-10-21T08:27:01.249Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/fd/4e8e6ee1917a978394bd8dfa4972ba98a106e426835ab7782667f38b04ea/pyobjc_framework_automator-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3cb965d6b3a6dcb2341fac4e33538b828e84a0e449e377c647f1cf44b7c19203", size = 10016, upload-time = "2025-10-21T07:55:16.911Z" }, + { url = "https://files.pythonhosted.org/packages/53/e1/ce7e8a938a5f7d8a8feffbedd8fa0615b8b5f92a66873d88e325af72fd85/pyobjc_framework_automator-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4d3f0f1733fb2e26c53f5d4a573c4d50a3246c591073756fc48f6127c96f0cd3", size = 10037, upload-time = "2025-10-21T07:55:18.552Z" }, + { url = "https://files.pythonhosted.org/packages/96/d0/f138a72276e6f5a43d5e8e0b4de9f3d22ee9f018b5871385bcbac14e4dbd/pyobjc_framework_automator-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e4e79a42f45602c6d971f9c33c3dab391fd2338b2feb62835b5fdf3137b3bce6", size = 10054, upload-time = "2025-10-21T07:55:20.203Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e4/13828164bffffd8e97f3bc0772a1756fa2854e17b50d3ff6605f16b8c53d/pyobjc_framework_automator-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1316ad293eadf1dda832303b7b05dc5fb435087e67a831f0b6b2d6f3b06d0cd0", size = 10198, upload-time = "2025-10-21T07:55:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/05/0f/e5a5e613afc279e3f080290aec787281cb60ebfe011e9e1d41b5c0d5c4c2/pyobjc_framework_automator-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a299effc6d1322f439e4d02d174fd9865610873c9a0e5d8868b7ae9038a8e563", size = 10104, upload-time = "2025-10-21T07:55:23.784Z" }, + { url = "https://files.pythonhosted.org/packages/2b/89/77c37ab4cb895e82da94163a3b99a5e2624ba050ab47bc7a04e29b02869b/pyobjc_framework_automator-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:db5e3b0fd4a9defaa316efc46ea6c62f4401befe4c5127955e77833c8f235b26", size = 10252, upload-time = "2025-10-21T07:55:25.421Z" }, +] + +[[package]] +name = "pyobjc-framework-avfoundation" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/95/29d3dbf7bfa6f2beb865ab4ce22ee1ccd58c2036a6c4caa6fa6568c7a727/pyobjc_framework_avfoundation-12.0.tar.gz", hash = "sha256:e9e9a15edea43341b39de677a58ac98b2a6bd4d6c55176b4804c5f75b3d20ece", size = 310508, upload-time = "2025-10-21T08:27:21.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/b6/cd14afee737a14b959ec9f96017134b80bdab55649b82f34f5490c060790/pyobjc_framework_avfoundation-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d47cd250011e6db5e20f1ff6ad72b6d2c40364eb6565009c7d2ff071e0a89647", size = 83319, upload-time = "2025-10-21T07:55:38.449Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3c/f9c732a33cafeff870e8d99c2378cc90a51f1a3261b5614f414b36902fdc/pyobjc_framework_avfoundation-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:803e675fbea532337bbd94becb040a054d58af610e20e86f7fd35fb54fd379f2", size = 83370, upload-time = "2025-10-21T07:55:45.122Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a3/07b098df03c1d5d8b4762ccba77881c9d41733a94db34815a27853531bf8/pyobjc_framework_avfoundation-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3aef07f73e8e908aeae195846d0d9bddb95bf82bbc10c22b51ec15f822a828fd", size = 83413, upload-time = "2025-10-21T07:55:51.443Z" }, + { url = "https://files.pythonhosted.org/packages/57/64/bffe9c7980313c84ef66f1c97770c12c505bc91a7e188a401f8655e85f91/pyobjc_framework_avfoundation-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:31840a514e703f64094c9f29053a0a22969b4666a207b5061d965fa0ddb96e4d", size = 83866, upload-time = "2025-10-21T07:55:57.81Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5a/40edf86b2c040070ca18c9eec2e2c52e7d111209279fee919b13ad86d2b2/pyobjc_framework_avfoundation-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:953bb5d6db3a6e2cddf489eb58bb6a1fb306e441ba6a011d04356b25c60a78e4", size = 83625, upload-time = "2025-10-21T07:56:04.163Z" }, + { url = "https://files.pythonhosted.org/packages/2d/6d/c6398333f88e2142d18ca9704413c5aa10d86fbc5ed813ded61da70104bc/pyobjc_framework_avfoundation-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a612ae9863abd4e0769ce6ff9960a5bf46128dddb3ef8f027406b8cd136e41f9", size = 83962, upload-time = "2025-10-21T07:56:10.484Z" }, +] + +[[package]] +name = "pyobjc-framework-avkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/76/65/2de0788c5ecde6906b9acfe1c37c6be59f9527eeb44b6fc494c63584edb9/pyobjc_framework_avkit-12.0.tar.gz", hash = "sha256:0f1ea37cd19483c62ba7a42e73dc07a03a0656ce916e772d13b017c625757930", size = 28881, upload-time = "2025-10-21T08:27:24.941Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/4d/087d8d19adda2478e314bbf27ae6f7de734fc4f8bca2c731c024bca167e7/pyobjc_framework_avkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dedab05ba28e6b2f09c72b8a232522e24980f250d7950f72a986edafd282c979", size = 11590, upload-time = "2025-10-21T07:56:14.304Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fb/1294cd716ac5e39eb6ff51ec6fa76a0cfecb657bbb5e446a63f188d4f783/pyobjc_framework_avkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5439fa8e4934fcdcf33b3e48d65ef7c1b9b016f7b41fb3af7023f4787fc33e9f", size = 11619, upload-time = "2025-10-21T07:56:16.108Z" }, + { url = "https://files.pythonhosted.org/packages/06/1a/880617bae980bd93ac49a5a9633aaf41db8cb10bf5154ada77b400d2490e/pyobjc_framework_avkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:172763e9c06da1fe074b35911e75d4db3d65bdd4e22bfd7c18083e787ccc6c3b", size = 11633, upload-time = "2025-10-21T07:56:17.773Z" }, + { url = "https://files.pythonhosted.org/packages/a0/db/6dad06275e722c05d138b8cef2582bb5fb8b3f396ec346563d7a1d540aca/pyobjc_framework_avkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:11abbbe482824aa5aaff0e6570be7567e5cb60b50abefb294da522e346149eca", size = 11838, upload-time = "2025-10-21T07:56:19.511Z" }, + { url = "https://files.pythonhosted.org/packages/32/51/38b9cff57e07d3443a53b67e825c476d304932538a5862f096272aca3a74/pyobjc_framework_avkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6a6990b18ac63b4f5d8e8792c7bc04b305505beb7a989bfa6c0d1203dfbbdd95", size = 11621, upload-time = "2025-10-21T07:56:21.301Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ce/7a4fab52c0ddeee6d4f25a9d85bfb2fcecd05f57c8fec14720b0c9f217a3/pyobjc_framework_avkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:52815d4b663c5ec4e1a96b23d2d3b0c7e03ff0ceca99d0a0475e9f0055c3c15d", size = 11838, upload-time = "2025-10-21T07:56:23.449Z" }, +] + +[[package]] +name = "pyobjc-framework-avrouting" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4e/98/cc2316849224736b9386189a52c80a73a154979a24c8877faa1be258a3b0/pyobjc_framework_avrouting-12.0.tar.gz", hash = "sha256:01edbba4257450bb42b87deb8c2498fc30e6d7a2adc9b25c81e118af5bdf7dac", size = 20432, upload-time = "2025-10-21T08:27:27.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/99/02cae8b7c7174a962677d817d5cee71319b4f30614ab988f571cb050b13b/pyobjc_framework_avrouting-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ee895f51745235db6ee32c9d1f807a9d0ca10f32c1827428b81a308670ff700b", size = 8446, upload-time = "2025-10-21T07:56:26.771Z" }, + { url = "https://files.pythonhosted.org/packages/84/b2/b7fed199a290539b77cfb597c068208ca16063c97de6bbacbadd2dc6a1b1/pyobjc_framework_avrouting-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ddc59275652aadc5332fef4d78460811968b9fc5f1c0f5bf7d0aea74df0fc40", size = 8459, upload-time = "2025-10-21T07:56:28.36Z" }, + { url = "https://files.pythonhosted.org/packages/ee/e9/a0ce79da974ddb40475621d2fbd42462063d57dc00238e49f27c49cedd24/pyobjc_framework_avrouting-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:58b3ef31ad0855df04ba9ca47e13a3d2cff8365d70a6d59708b747b22fd2e9a0", size = 8479, upload-time = "2025-10-21T07:56:29.972Z" }, + { url = "https://files.pythonhosted.org/packages/01/7a/0c10711dad7c1e7022427e0db7515ee3051042b3af95f7f680f1af0bbc47/pyobjc_framework_avrouting-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2d97593d312f7c1eb9cc3df3d9c82d9124130567a579641ff976d594e1d6b371", size = 8638, upload-time = "2025-10-21T07:56:31.622Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ea/e248c709473d7cc50b6ffce8243aef737b74fe597aa5d9beb929dacb4115/pyobjc_framework_avrouting-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:210f20df144aa56d04b3834ffc46423880de6361ac6b63bbb63daa602cfc0d95", size = 8531, upload-time = "2025-10-21T07:56:33.939Z" }, + { url = "https://files.pythonhosted.org/packages/b0/89/d5726926189ccb42acd0df50b50cf95c99d24957539d1a8bc49e881930e8/pyobjc_framework_avrouting-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:644cadc54028efb991e2db74eed582e2278fec90d3a783475cf62afaba8e6af3", size = 8701, upload-time = "2025-10-21T07:56:36.684Z" }, +] + +[[package]] +name = "pyobjc-framework-backgroundassets" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/d6/143de9d93121fae5201c18ca3b5dcf155f3abc6cabed946ab20f52b99572/pyobjc_framework_backgroundassets-12.0.tar.gz", hash = "sha256:f9bcfba27ffec725620e87778a26b783e3955343adcc96e3d5635edcc4cb1207", size = 26625, upload-time = "2025-10-21T08:27:29.629Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/87/3972cda9f3462066fa95d8b620f786abf4aea056cc5a955d4c2d52e21966/pyobjc_framework_backgroundassets-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0a7b24f58146d2e03b5d8de1f8ea26d313f791328f2f6067f720e15e84f64f", size = 10771, upload-time = "2025-10-21T07:56:40.052Z" }, + { url = "https://files.pythonhosted.org/packages/84/32/e33d4ba57327864438b618a746a419b0ea7909e0c5eae6e22d9918c211b7/pyobjc_framework_backgroundassets-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b6fda04cf78782d70410dcd0c3a72fa43b011b7ad9d72418a5a935e41200c4dc", size = 10789, upload-time = "2025-10-21T07:56:41.781Z" }, + { url = "https://files.pythonhosted.org/packages/17/c2/7c742e87a02763f2523618659db1d6c48a7f92c3cadc06b73411a6710e19/pyobjc_framework_backgroundassets-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2cd40f12d97d0993894fdccfac6eebf6787decf0c13c0213e723ef62abf1f00e", size = 10813, upload-time = "2025-10-21T07:56:43.715Z" }, + { url = "https://files.pythonhosted.org/packages/ec/72/2ee6f418c72d0f0617cb03c01ad88473c46580441e59b7c1f98571114895/pyobjc_framework_backgroundassets-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7542ec038356046ecc790379d059ea6ae381eada7a75b4b342d6788230508f45", size = 11069, upload-time = "2025-10-21T07:56:45.367Z" }, + { url = "https://files.pythonhosted.org/packages/d2/08/6ebf4147a2185ec12fae1d6dfd481d30d5b1cfebf7a18ac7ad5041fb016e/pyobjc_framework_backgroundassets-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7e1f44669c110150a65b765e3a92a1538dc925b037a6d7e50c156a24062ab83a", size = 10864, upload-time = "2025-10-21T07:56:47.042Z" }, + { url = "https://files.pythonhosted.org/packages/a3/85/f4e20f74b9741fbb7d8174e18b1729d9a491fe4221a8b88d6e2d2e43f408/pyobjc_framework_backgroundassets-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:5df2618f89d14ea0084afa59d044c6342c8a394d5368c85965055cc44f08b4e6", size = 11069, upload-time = "2025-10-21T07:56:48.981Z" }, +] + +[[package]] +name = "pyobjc-framework-browserenginekit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/a3/fe0015c88f576e42702a96c33d9d8c4f0195f32017f81d224e3f2238905b/pyobjc_framework_browserenginekit-12.0.tar.gz", hash = "sha256:8409031977ee725b258e96096a2ad2910c11753865d8e79aa6c8c154a98a55a6", size = 29480, upload-time = "2025-10-21T08:27:32.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/e9/dd169256d5693f9f35ed3169009ba70544c305f90a34ccbc79b0f036601b/pyobjc_framework_browserenginekit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce95e87b533c12fc70dcf10c7ca4ec6862ea00dd3ee076b8b0f6f66110771771", size = 11531, upload-time = "2025-10-21T07:56:52.905Z" }, + { url = "https://files.pythonhosted.org/packages/34/cc/98765d9f39fbbdca3ecd72c1ef2d2b68e35922b2c482f0f73fae30933f49/pyobjc_framework_browserenginekit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8597505099922d4469208a42947d8baf4b1ba82c9793281686f92c62fcf1a7f", size = 11556, upload-time = "2025-10-21T07:56:54.707Z" }, + { url = "https://files.pythonhosted.org/packages/11/e5/b732d765d0f48c4559fdef85aacee030fb31614eeb138aaf149a34a5ac42/pyobjc_framework_browserenginekit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3d290dfcb353828ee0220c3026c1920572c4b04d9fdf9934349988d2ad1ddc58", size = 11575, upload-time = "2025-10-21T07:56:56.513Z" }, + { url = "https://files.pythonhosted.org/packages/a9/59/9c5a0bcbbdd964b42a416b085a0ea7d8ba369130ada44956b1507b54850c/pyobjc_framework_browserenginekit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e22aada3a3dcf0cec5dae7856aaacf05ea38bfb8e1e69d15956bc8fb52f61cd6", size = 11748, upload-time = "2025-10-21T07:56:58.677Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d7/085cde585aef2d3601e745e2a2f101abca2a8ca761a0567c9cfcca524564/pyobjc_framework_browserenginekit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6688b3a109158a4734cefff7ca866e85550d3d10f3fa12d09268fbe174521370", size = 11629, upload-time = "2025-10-21T07:57:00.753Z" }, + { url = "https://files.pythonhosted.org/packages/e7/10/89141c5fa3492f06740104850b95995232c14f84305dfdd9a463681663bc/pyobjc_framework_browserenginekit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d4b5809a751bf0f3d24773034763c57b139fff5eeef22f07ce760d14b0f83e2a", size = 11818, upload-time = "2025-10-21T07:57:02.82Z" }, +] + +[[package]] +name = "pyobjc-framework-businesschat" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/74/a34367bab4b74126897e37b5838e47c135407950bd843fddd115ffb75428/pyobjc_framework_businesschat-12.0.tar.gz", hash = "sha256:2f598056f1a90a5a85ef3c75c8457f8cd80511017982a17ddb28695a6bf205f6", size = 12127, upload-time = "2025-10-21T08:27:34.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/41/3f41a8a7c2443cc8e2d6a6cbc19444d9a56ebd000b16246573fc5bb6d2f1/pyobjc_framework_businesschat-12.0-py2.py3-none-any.whl", hash = "sha256:a3faa5a6be27fd18f2b0d34306d8cb8e81c1f2c1f637239b4c9b9f5d90e322ee", size = 3482, upload-time = "2025-10-21T07:57:04.105Z" }, +] + +[[package]] +name = "pyobjc-framework-calendarstore" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/6d/62bf488ca94108fa8820a691b41da62aa69daeef3bca86f14af1f576a5a3/pyobjc_framework_calendarstore-12.0.tar.gz", hash = "sha256:cfdac6543090d7790c576e24ff87440d3b57e234a51e9468bdbb5451b4d94c9b", size = 52284, upload-time = "2025-10-21T08:27:39.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/f8/678b8725046e320a3183c232349af205567b0489dda818eb7572a1a7b8e0/pyobjc_framework_calendarstore-12.0-py2.py3-none-any.whl", hash = "sha256:32432f4fddf080f8a5d592a2dc659f30bde9486c89dc0978fee5faec7847a076", size = 5295, upload-time = "2025-10-21T07:57:05.732Z" }, +] + +[[package]] +name = "pyobjc-framework-callkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/2a/b0ed29456b1d55bb2764768bcd2668cbf2f746a27a67854da71d89e4609b/pyobjc_framework_callkit-12.0.tar.gz", hash = "sha256:fab030e3e5c33d245f3b00165b5cf366ae43846ce237e3d4a0874198c17d8d60", size = 29544, upload-time = "2025-10-21T08:27:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/be/0d3e91da5b873759373590e5fa7b0de5f3d3ecc57fbda8a659240906183f/pyobjc_framework_callkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:baff4db6c268f18e4035d136d10e9fa4a58504ff41e201a7a2148aa91b4e0797", size = 11282, upload-time = "2025-10-21T07:57:09.961Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a0/57bba44c67534455e8bbdd004be177697f76e59dd7ab4153cb0bc08fe37e/pyobjc_framework_callkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31e6b21d479892d3736ee0ab6c68571b070c846be42b0c07640f1495a14b32db", size = 11345, upload-time = "2025-10-21T07:57:11.876Z" }, + { url = "https://files.pythonhosted.org/packages/da/3c/d0f193229bfc95a5022479ce3812e8e0cada5aad35bcf291aec1e794e4f4/pyobjc_framework_callkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:143a1edb64a3d17f7a379a50200b220f060b0f89e29f4ee4e098ef9c47dd90f5", size = 11356, upload-time = "2025-10-21T07:57:13.651Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/e7ae42e301c5052893be17be5fadfb137097aa41baf0edc07bf56b444f6b/pyobjc_framework_callkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ad37a033b7ed1bccec7dcabcc297e97a9b16064723805d9eda9e9fad2b659fba", size = 11568, upload-time = "2025-10-21T07:57:16.159Z" }, + { url = "https://files.pythonhosted.org/packages/1c/45/ea7638c053678bf82d58a270ae7991408d4dfa352ca92bf9cea63d461d52/pyobjc_framework_callkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:07fc2b314ccfe0b192ca69c810e4adba2990b31c1bb6bfbdbd3794501ae00982", size = 11348, upload-time = "2025-10-21T07:57:18.007Z" }, + { url = "https://files.pythonhosted.org/packages/80/75/19366317f39e02cfde6ca578c7cd0012bd7a7b227b4f0185a3705c3657ec/pyobjc_framework_callkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fca661ce7212e90f39cf30e3793c54beeac60d8cb36f6d2d687eef775bc468f1", size = 11567, upload-time = "2025-10-21T07:57:19.685Z" }, +] + +[[package]] +name = "pyobjc-framework-carbon" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0a/86/e5212c091d614f5097fb34d06820fda00d4dc2dcc0ac68d102b8cb0a79ac/pyobjc_framework_carbon-12.0.tar.gz", hash = "sha256:ad24c6c9def13669f9b6dc2350b39ac96270f4918223d1abf4d8a70990eed84c", size = 37320, upload-time = "2025-10-21T08:27:45.651Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/aa/56b0bc78523ca3ecdf6e72a8b786b7204364c57d1b2db17bb50cfed1091d/pyobjc_framework_carbon-12.0-py2.py3-none-any.whl", hash = "sha256:b58d0f558f3f31e981c26a1074fce8a32bf0aa6f9c6bccefdb2828a4f9c46eac", size = 4635, upload-time = "2025-10-21T07:57:21.073Z" }, +] + +[[package]] +name = "pyobjc-framework-cfnetwork" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/92/910990becf6e6205787a9e1a1ce6847358fab73b76949283a053c7cd8d54/pyobjc_framework_cfnetwork-12.0.tar.gz", hash = "sha256:b6c3d156c774f8c5fc2bfb3efc311c62cfd317ddaffb4d6637821039e852e3f1", size = 44831, upload-time = "2025-10-21T08:27:49.303Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/34/8905bb4c86d89c6e502f3ba2dddaa436db18d532b0b535b101b8883759f9/pyobjc_framework_cfnetwork-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fa4217f7d855d988e7f6799ed3941e312990d4e1d2ce43820e581c87c5383fe2", size = 18957, upload-time = "2025-10-21T07:57:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/a6/bf/f78bb4ea0d1e1d83c2e75b24eba37b3ab5caf14a212cf11a43d7b83fec48/pyobjc_framework_cfnetwork-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:912b07f050fea73345015daa9c46a7aeaac3b3b711682e6bf4686e994cd2d7cf", size = 19140, upload-time = "2025-10-21T07:57:28.202Z" }, + { url = "https://files.pythonhosted.org/packages/f9/79/076af9b27dfee72f2a383812efbc4206bdae02ddcfbc2267c914a135d0e8/pyobjc_framework_cfnetwork-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1b02b95f7f0be4a4bd5c2ba468528daded3dea05641b01133c4cbab37f31254d", size = 19144, upload-time = "2025-10-21T07:57:30.331Z" }, + { url = "https://files.pythonhosted.org/packages/c2/72/c0de6704a6c3351149391892eb5fe8009260355070487c0bf9a9c28cf7f7/pyobjc_framework_cfnetwork-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f26e05d1b7f5e4af3b2dfe3e1d443ab09d625a3b3d6007ec84e851ca02e8f383", size = 19422, upload-time = "2025-10-21T07:57:32.953Z" }, + { url = "https://files.pythonhosted.org/packages/b7/96/ea7607704670a886b94c39e1a4fbd8b2b43a8321369937652935c3023889/pyobjc_framework_cfnetwork-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8f313ed9b11e203ea4be80f2310819749d99c5a4554293467269e0a6db9952f1", size = 19192, upload-time = "2025-10-21T07:57:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4c/837eeffd0f3456dd8f2fc7055c9394006769d28c8ebd5cfb82182a9bf5a7/pyobjc_framework_cfnetwork-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:653a350813a0d10935b191b7d56227a1b7dce6a6e2d43bbaf758233126f581ab", size = 19415, upload-time = "2025-10-21T07:57:37.835Z" }, +] + +[[package]] +name = "pyobjc-framework-cinematic" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-metal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/73/803108294b8345056fcfdd592e4652155080b47fc1f977bcbac6d360adab/pyobjc_framework_cinematic-12.0.tar.gz", hash = "sha256:4b0592f975a24192ef46f28b5ea811c2a7ed15d145974da173c93f39819b911f", size = 21218, upload-time = "2025-10-21T08:27:51.939Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/38/9779f870b59383d063030d095d50e7a37e3f1f11e5ba782a6fdbaab5cbe6/pyobjc_framework_cinematic-12.0-py2.py3-none-any.whl", hash = "sha256:2c8a4e862731a623e7a4c29e466a4ad9ee7630653567aa32c586914e16f91ae7", size = 5042, upload-time = "2025-10-21T07:57:39.419Z" }, +] + +[[package]] +name = "pyobjc-framework-classkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/a5/e6a3cb61d2e7579376c11282c504445e5ad38c9cd6220f62949b863ef5df/pyobjc_framework_classkit-12.0.tar.gz", hash = "sha256:a8511b242a7092e79e0f97cc50f0f2fe4b28f92710f3c3242247334227818820", size = 26664, upload-time = "2025-10-21T08:27:54.802Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/91/963ffc9575e5b0757911fef921ed668ec642ba3916faec58717a4f5f82dd/pyobjc_framework_classkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86a8d5c8c56ec8c9592020ac6c50bab82f81e48e382a95f0f5ef7b2509117315", size = 8867, upload-time = "2025-10-21T07:57:42.883Z" }, + { url = "https://files.pythonhosted.org/packages/f9/59/1bdf42a95f5af3316e4669991c2558cfbf877b350e021305c1ff286818ee/pyobjc_framework_classkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c3bb3523259eb3d6583a9e8605f5932321d833840c56e1a8a720eb12d3a1f2cd", size = 8885, upload-time = "2025-10-21T07:57:44.502Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f2/54ce6f6013b051021d95db651a4115a340c37fa00c9e30238bdc43064188/pyobjc_framework_classkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d6c5968cbca3b3cbbd2fb91e46e2716a43dce910206bc84192cac145c8d17dbd", size = 8890, upload-time = "2025-10-21T07:57:46.091Z" }, + { url = "https://files.pythonhosted.org/packages/55/bf/b121f3da28787091db6d654bde4bff288ace26071ef466b6fd8b878ec833/pyobjc_framework_classkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:34c3881f97b996ce0b80210f0d3435ecec4be2a23a931e231f463ca54ac047d4", size = 9051, upload-time = "2025-10-21T07:57:47.93Z" }, + { url = "https://files.pythonhosted.org/packages/49/6c/2e60e91750624a907c8d10ae4a7f2034f680f47625912be14a7ad53ee7d1/pyobjc_framework_classkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e0d837c35d996f86d11aa84031ed26060eb9db10423d3f6dc78affc0688e42f3", size = 8966, upload-time = "2025-10-21T07:57:49.926Z" }, + { url = "https://files.pythonhosted.org/packages/72/1f/2a2dbc163ff34b1965a1f842ee651145579e5ab64cdb367785ae67c7455b/pyobjc_framework_classkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c08ed6c0f2e2272bb86491a8bf19662d94ccdee34d34c0ce4a40a734ba5508a1", size = 9117, upload-time = "2025-10-21T07:57:51.877Z" }, +] + +[[package]] +name = "pyobjc-framework-cloudkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-accounts" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-framework-corelocation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/83/dc/539f3a4c2b490adc2079f111b6594e847cd9fdb10d44b65b629977673c44/pyobjc_framework_cloudkit-12.0.tar.gz", hash = "sha256:1ac29d81005b92575ce6a5c9bdbb8fec50cd9fadaaab66db972934e5e542cf1c", size = 53756, upload-time = "2025-10-21T08:27:59.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/67/5bbc583777376642c103a327930c11bca0c3eb3a1ceaad20dfaf55be96eb/pyobjc_framework_cloudkit-12.0-py2.py3-none-any.whl", hash = "sha256:1ad9af5c0ef94e147cd8c5676aab7925ead9da8398bd01898597c4da7cb3231b", size = 11102, upload-time = "2025-10-21T07:57:53.771Z" }, +] + +[[package]] +name = "pyobjc-framework-cocoa" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/37/6f/89837da349fe7de6476c426f118096b147de923139556d98af1832c64b97/pyobjc_framework_cocoa-12.0.tar.gz", hash = "sha256:02d69305b698015a20fcc8e1296e1528e413d8cf9fdcd590478d359386d76e8a", size = 2771906, upload-time = "2025-10-21T08:30:51.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/7d/1758df5c2cbf9a0a447cab7e9e5690f166c8b2117dc15d8f38a9526af9db/pyobjc_framework_cocoa-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae041b7c64a8fa93f0e06728681f7ad657ef2c92dcfdf8abc073d89fb6e3910b", size = 383765, upload-time = "2025-10-21T07:58:44.189Z" }, + { url = "https://files.pythonhosted.org/packages/18/76/ee7a07e64f7afeff36bf2efe66caed93e41fcaa2b23fc89c4746387e4a0d/pyobjc_framework_cocoa-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed99d53a91f9feb9452ba8942cd09d86727f6dd2d56ecfd9b885ddbd4259ebdd", size = 384540, upload-time = "2025-10-21T07:59:09.299Z" }, + { url = "https://files.pythonhosted.org/packages/fb/29/cfef5f021576976698c6ae195fa304238b9f6716e1b3eb11258d2572afe9/pyobjc_framework_cocoa-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:13e573f5093f4158f305b1bac5e1f783881ce2f5f4a69f3c80cb000f76731259", size = 384659, upload-time = "2025-10-21T07:59:34.859Z" }, + { url = "https://files.pythonhosted.org/packages/f1/37/d2d9a143ab5387815a00f478916a52425c4792678366ef6cedf20b8cc9cd/pyobjc_framework_cocoa-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3b167793cd1b509eaf693140ace9be1f827a2c8686fceb8c599907661f608bc2", size = 388787, upload-time = "2025-10-21T08:00:00.006Z" }, + { url = "https://files.pythonhosted.org/packages/0f/15/0a6122e430d0e2ba27ad0e345b89f85346805f39d6f97eea6430a74350d9/pyobjc_framework_cocoa-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a2b6fb9ab3e5ab6db04dfa17828a97894e7da85dd8600885c72a0c2c2214d618", size = 384890, upload-time = "2025-10-21T08:00:25.286Z" }, + { url = "https://files.pythonhosted.org/packages/79/d7/1a3ad814d427c08b99405e571e47a0219598930ad73850ac02d164d88cd0/pyobjc_framework_cocoa-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:32ff10250a57f72a0b6eca85b790dcc87548ff71d33d0436ffb69680d5e2f308", size = 388925, upload-time = "2025-10-21T08:00:47.309Z" }, +] + +[[package]] +name = "pyobjc-framework-collaboration" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/df/611e4f31a4ad32bc85d39f049006d7013fde6eec57f798714d13c3e02c70/pyobjc_framework_collaboration-12.0.tar.gz", hash = "sha256:7090d493adeffee2d6abcf2ce85d79cb273448b7624284ea7ede166e1a9daf7f", size = 14322, upload-time = "2025-10-21T08:30:54.394Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/a7/02070855162d0b997884fffcc42976cead4de3e764f7b3b234fd9c23f2b2/pyobjc_framework_collaboration-12.0-py2.py3-none-any.whl", hash = "sha256:f3d5bf79ed1012068c279b46225b23236e4c099d549421192c89468d591c40cc", size = 4915, upload-time = "2025-10-21T08:00:49.897Z" }, +] + +[[package]] +name = "pyobjc-framework-colorsync" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/81/efc29f6af5fb9c1c483c3035c3020e0e6932f8d975972e0f9c71a31615f6/pyobjc_framework_colorsync-12.0.tar.gz", hash = "sha256:9733cef2d4641cbd308fc3f33b8fba07f34ed1e58bf45a4d982289c9c6706156", size = 25015, upload-time = "2025-10-21T08:30:57.019Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/10/6e1025a7aaa9b7d5bbd97b0ff462a40880b0ded608e7ec5c87c5f50100ae/pyobjc_framework_colorsync-12.0-py2.py3-none-any.whl", hash = "sha256:68c24293b0613796521172964c2b579b76794bcbb62f1d045ef5539e60b91626", size = 5963, upload-time = "2025-10-21T08:00:51.87Z" }, +] + +[[package]] +name = "pyobjc-framework-compositorservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-metal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/0c/e7e6b4b329691804bf4dd5a4c05e7e3432b929265c914e38d09de80b629b/pyobjc_framework_compositorservices-12.0.tar.gz", hash = "sha256:c2d47153e6d180d0040235b8a61d58d1c9659f55df933fd4f16a55f281fcf9c9", size = 23309, upload-time = "2025-10-21T08:30:59.5Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/26/83bf8f230ae22ab531c2870ef33a85c3d36aef05d3efd0a5899a68531b96/pyobjc_framework_compositorservices-12.0-py2.py3-none-any.whl", hash = "sha256:71f98346eb05c240a3b4c3f0d5399dbadd4dbb73b74bea24600065c9ef9d453f", size = 5918, upload-time = "2025-10-21T08:00:53.527Z" }, +] + +[[package]] +name = "pyobjc-framework-contacts" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/47/fb/9e60e4db4a4f4c02be4b0ba2d59ea116db230e1f4de134247d3390168dcb/pyobjc_framework_contacts-12.0.tar.gz", hash = "sha256:ac921f8ef7bf3767b335d8055f597b03ad6845dfd93c05647cf41550af6dcda3", size = 42727, upload-time = "2025-10-21T08:31:03.189Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/94/55c18e908a9e25e47b2649e1c9ac4a5eb79d4d8595cf2585324d00ce32c5/pyobjc_framework_contacts-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1929f3c9de057542da9d292d8ab0d40dfc086b24acf50739f7d590ac7486d13d", size = 12093, upload-time = "2025-10-21T08:00:58.044Z" }, + { url = "https://files.pythonhosted.org/packages/24/52/3e7639e42f457b4890e9f847c3e54eeada34e888602e11fcc4e7418475e2/pyobjc_framework_contacts-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:29f8a0253c251e5b699cdf392004f130190df53e53ba1fb40e7cd1b64ed1383d", size = 12175, upload-time = "2025-10-21T08:01:01.028Z" }, + { url = "https://files.pythonhosted.org/packages/df/27/da5ffb07e7b0a54f5c16d99ebffe4e7407204681e2aa03efa4d47792a669/pyobjc_framework_contacts-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5b9892f560586295fd9d8e87610add3417c36564a5cc3af70baf64f662024b56", size = 12183, upload-time = "2025-10-21T08:01:02.877Z" }, + { url = "https://files.pythonhosted.org/packages/e7/e2/d3f8fe4cb9018086b4dcea1090533cd3fc44ff99ffc809e5f5fef6845d8d/pyobjc_framework_contacts-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:70469137f625a909becee54770c1134766d6a9367f19027b9b04f04d673ce2d0", size = 12352, upload-time = "2025-10-21T08:01:05.025Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8a/a9b64fddb086bfe34bbf12a791876b892d274666557188dea9232233c4db/pyobjc_framework_contacts-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:01f58f1b5c49b1cfe9bfc3dbebc00ca48962000b7d40fbeb1a9f25e2b03732ed", size = 12268, upload-time = "2025-10-21T08:01:07.201Z" }, + { url = "https://files.pythonhosted.org/packages/88/56/55ddc21dd30d971e7a3f55b18431f49ffd9cce1cafbffeb953c84e839c3f/pyobjc_framework_contacts-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:14f80cfc5b77e4db87c5e679ad7f864435a732e55fd1158a046383603e8224d8", size = 12423, upload-time = "2025-10-21T08:01:08.939Z" }, +] + +[[package]] +name = "pyobjc-framework-contactsui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-contacts" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/64/9b/eb41bfdad0a2049f27559e0d152b1bb6cc1d001cc9ebf97fb94f548bc3ea/pyobjc_framework_contactsui-12.0.tar.gz", hash = "sha256:98bed7b93b0934786f6ddd9644c80175a40a593a0a4ffd8128ef7885bc377f5a", size = 19163, upload-time = "2025-10-21T08:31:05.826Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/bb/0aaf1fc166646156a746fad066a50d2191aa06e975bb9f55d880633e0ead/pyobjc_framework_contactsui-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ffc7837b2bbddc1c4e830bcee07d976f87a2827422f16fd7612fe8b1fd4332a1", size = 7880, upload-time = "2025-10-21T08:01:12.55Z" }, + { url = "https://files.pythonhosted.org/packages/f6/50/1ff9219c73335ddbe85099fe09d8f02030a5ff2dd1e839167b67916477dc/pyobjc_framework_contactsui-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9005c08196dd4fc5d338579163391e969354905f312639816683b4976ea496b5", size = 7899, upload-time = "2025-10-21T08:01:14.39Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ff/05321db2ce7979dd8d0137a919734e8608990c7a8323e7bfaeed283a3750/pyobjc_framework_contactsui-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4ee9afcc857434147939e53d7190582f919660f7bc7c44b3a2682cb61f639162", size = 7914, upload-time = "2025-10-21T08:01:16.813Z" }, + { url = "https://files.pythonhosted.org/packages/19/b9/30e4db40690ecee1c84dcdcf445f65378b54cebb0bd650faa92caff231e9/pyobjc_framework_contactsui-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:93cae23de7d80bec4de6241f10328a40581360e6b4ed7510deb004290068f2e5", size = 8061, upload-time = "2025-10-21T08:01:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/15/c1/14d8afd208cc8f03dc67d68027bd28b71a1dec0a7635662626584617e7b8/pyobjc_framework_contactsui-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fe9081e485b4be4c9062f9d9764f0cad969effb20ff98fa2b51fc6db478e33f5", size = 7968, upload-time = "2025-10-21T08:01:20.578Z" }, + { url = "https://files.pythonhosted.org/packages/7f/02/91454deed58153c97ad07a93c70179714c3ca9ee4821d32eeace3a3ada4a/pyobjc_framework_contactsui-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4bd37e9024336302e021459b2b9098e463d8e6ef96a9bebe79285d043bb79a7a", size = 8122, upload-time = "2025-10-21T08:01:22.465Z" }, +] + +[[package]] +name = "pyobjc-framework-coreaudio" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/a0/604b8e2e53f46536b9045fc0fbfa9468a606910c9c0a238d0f3d31071d87/pyobjc_framework_coreaudio-12.0.tar.gz", hash = "sha256:19741907d2d80a658d3721140eb998061007955323b427afca67eda0e2ad3215", size = 75415, upload-time = "2025-10-21T08:31:12.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/42/284cc68a2bd310f4399eb92e5259319a3131b1fba5f1496dfaa477eaaed0/pyobjc_framework_coreaudio-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d6287d67c7b3ca9abf4b7e8a64e1a05e97ebcb52b32e92a78e1e825d1334ec56", size = 35337, upload-time = "2025-10-21T08:01:29.747Z" }, + { url = "https://files.pythonhosted.org/packages/51/49/97cbda2efdb02e9d8c8507dc980040056b96ca9604dab41cbed3c874fe4a/pyobjc_framework_coreaudio-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c89762834680a26436a8e435dc736b509f1c3aa3927f66db85d3289995d007d2", size = 36920, upload-time = "2025-10-21T08:01:33.428Z" }, + { url = "https://files.pythonhosted.org/packages/52/c1/8bd4c6a917d7314042a7b26f3433c680c051f64995da682a5f99502202c9/pyobjc_framework_coreaudio-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f7309087b42ce6c399d2971a7173c9216c03a43a998bb2be2eecc90fb362ccb2", size = 36944, upload-time = "2025-10-21T08:01:36.973Z" }, + { url = "https://files.pythonhosted.org/packages/84/92/23ab5d0f3b953bb944d7bbb99d054c560b9a2d931d173e9165b44172ebb8/pyobjc_framework_coreaudio-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b52a2ef28b557c5f5cbf97264ce0c6f8ce1a4ea0309b4a952122b9bc3a4ad636", size = 38398, upload-time = "2025-10-21T08:01:40.422Z" }, + { url = "https://files.pythonhosted.org/packages/e2/14/d7f6b39f0234de213889df52091681b9abab9e4b7ca6858eff1cbe5e3c14/pyobjc_framework_coreaudio-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:30ac30f6be6b35bbe7f21c055269de6643c378c5e15bf5002c4eb1de942904fc", size = 37021, upload-time = "2025-10-21T08:01:44.003Z" }, + { url = "https://files.pythonhosted.org/packages/34/ee/f1e955191775df1cdac142bfca1dc2787c9dde9f23e821061c7a18ff6e86/pyobjc_framework_coreaudio-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1bb16d186466cf3b9c23e29dbc0470c282c7194dc022b685f075a7585dfc8a43", size = 38498, upload-time = "2025-10-21T08:01:47.554Z" }, +] + +[[package]] +name = "pyobjc-framework-coreaudiokit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/4e/9c55aa44e330cbbecf47c41fd1804128057422ae9ef2349db8c122c9ffb2/pyobjc_framework_coreaudiokit-12.0.tar.gz", hash = "sha256:2f02896167adf3f420ab8dd55a41c905e42ed59edf21a6f5f6d4d2f16b8b67a8", size = 20519, upload-time = "2025-10-21T08:31:14.66Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/b3/c5723b94ba5d054971b8e6e5d4cefbd7664892556259e41fd911202227f9/pyobjc_framework_coreaudiokit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0ddca463bd0adc3cd67ef2ae345c066f792ebddd8113903e06e2b6bab23750e3", size = 7256, upload-time = "2025-10-21T08:01:51.444Z" }, + { url = "https://files.pythonhosted.org/packages/82/af/3b5a9b306b8d605fe6ade3c38ea6603a845c78c53c648d7d849e9670788e/pyobjc_framework_coreaudiokit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d9caad5d1e560dbe013d41a29a7ae0b38b99cacaadb60e94a58cb15430af80db", size = 7280, upload-time = "2025-10-21T08:01:53.003Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4c/377cf6bba1282ab5f02da2bbb2ddf9d4a7f68124096f5f0c712292d6294f/pyobjc_framework_coreaudiokit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e6dd90bee277d320198041ca54986af9a985dda5ee9a97910f446ab43bb1379a", size = 7295, upload-time = "2025-10-21T08:01:54.489Z" }, + { url = "https://files.pythonhosted.org/packages/5d/70/a851e968af8b523ed8e194dcb9b232baffd2448c6c4f85daac91d143b68c/pyobjc_framework_coreaudiokit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c34d09d49e2b5ce3bc40bc91db6616807aa34f7d88a75dcfd89d5e6184fe4186", size = 7449, upload-time = "2025-10-21T08:01:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d4/207c787fd2522df4ea14838f73979d31a69a70c2d0fec227eb36c0ff7bfa/pyobjc_framework_coreaudiokit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:48b4a04dcb825567bcf6aca1e9145ed68722f82e081d6db0cb0330d3dfca2190", size = 7359, upload-time = "2025-10-21T08:01:57.572Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7e/599499bf4ebc7a81fb900107e334f4ba0e57cb38423c5c85c9904180349b/pyobjc_framework_coreaudiokit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2be1a9f95a4e24c7cd18a8bbe2a3173a14aa60a4edc830bb341a4ac4d2189265", size = 7514, upload-time = "2025-10-21T08:01:59.038Z" }, +] + +[[package]] +name = "pyobjc-framework-corebluetooth" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/b2/ad9e8516cd73611a3a8f8ff2d7d51b917115f3f7f9e7a9760d5fc4e9dd6b/pyobjc_framework_corebluetooth-12.0.tar.gz", hash = "sha256:61ae2a56c3dcb8b7307d833e7d913bd7c063d11a1ea931158facceb38aae21d3", size = 33587, upload-time = "2025-10-21T08:31:18.036Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/ef/4190181375f38d1223cd022fb526cc1ec1c1708937482203141ab1238fbb/pyobjc_framework_corebluetooth-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ab59e55ab6c71fcbe747359eb1119771021231fade3c5ceae6e8a5d542e32450", size = 13200, upload-time = "2025-10-21T08:02:02.933Z" }, + { url = "https://files.pythonhosted.org/packages/04/7d/628c3711e2fd13864217b1984ebef815d774caf2806b4366b3ed869e6ee3/pyobjc_framework_corebluetooth-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b5b3b276efb08a1615932327c2f79781cf57d3c46a45a373e6e630cd36f5106", size = 13226, upload-time = "2025-10-21T08:02:05.785Z" }, + { url = "https://files.pythonhosted.org/packages/9c/7e/8d6c430d6a282ea496373ef210d451ae716e8ceea1a6a5b3a1155b793150/pyobjc_framework_corebluetooth-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba02d0a6257cb08a86198e70cb8c0113c81abf5f919be9078912af8eaf6688ae", size = 13241, upload-time = "2025-10-21T08:02:07.722Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1a/e879130406efdbef2067245af85bbb9ae0053a8e80e69a3603926e1a6cd1/pyobjc_framework_corebluetooth-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7696dbb61074ac657d213717869c93e6c3910369255f426844b85f4b039fb58c", size = 13425, upload-time = "2025-10-21T08:02:09.948Z" }, + { url = "https://files.pythonhosted.org/packages/b6/bf/68d2c3c90039265c94b69d3091c8c8af94b1107f38898b49bd88acb81ae0/pyobjc_framework_corebluetooth-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a50ff5e5ef5df8fd2b275fadbd51f44cec45ba78948a86339e89315909d82bd6", size = 13233, upload-time = "2025-10-21T08:02:12.927Z" }, + { url = "https://files.pythonhosted.org/packages/80/b7/fd0563e15d17746695247f247e9cdaf56ebca47b4db72c6a882e861fb2fe/pyobjc_framework_corebluetooth-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:000d3a863fcd119dbdc6682ebe4cc559e2569ec367a7417ac2635c3f411f7697", size = 13423, upload-time = "2025-10-21T08:02:16.063Z" }, +] + +[[package]] +name = "pyobjc-framework-coredata" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/ad/391d4c821c37ccf1a15ac13579c8f1eac8114a95b97d5904c9566ad4d593/pyobjc_framework_coredata-12.0.tar.gz", hash = "sha256:b9955d3b5951de8025cb24646281e42e85f37233150e4c7c62f1e2961088488b", size = 124704, upload-time = "2025-10-21T08:31:26.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/50/11f57e33b290bc3d34a7901584761965bf273248ddc0ef9eab276e2fa709/pyobjc_framework_coredata-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e51e6b80bd9151fe09be4084954c26f8c4332367bf2ea60347617491b477152", size = 16401, upload-time = "2025-10-21T08:02:20.787Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a4/68f5c43b795deb188be5bdbabd0b284e8610591de35b2bfbd22ae2841d40/pyobjc_framework_coredata-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:160d0348e7b03a6248c1810b1e493bb1a6c3bf4c4eab2577fc45b20967ff56ee", size = 16413, upload-time = "2025-10-21T08:02:22.861Z" }, + { url = "https://files.pythonhosted.org/packages/d3/6d/bc0fd51b3d06f3cc7a555b8c16a4ac1194db213f4549e80802d0683eba05/pyobjc_framework_coredata-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a85947442d8aad572e54a9459f7285f69fcc5643b4fbec03bfad12d35ab23434", size = 16425, upload-time = "2025-10-21T08:02:25.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/62/af7bef77d6db9ee0f18e03017eb012a767ae495791b576815251f8aa5f89/pyobjc_framework_coredata-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:057e8e0535a39ed6f764dd840fbb99dee58d55944aab00258ba50edcf0ce9778", size = 16583, upload-time = "2025-10-21T08:02:27.336Z" }, + { url = "https://files.pythonhosted.org/packages/f6/4d/22371987fcf1ab81697fbacfb1424f6a3fcf6826617fbb03d17ef537f0e0/pyobjc_framework_coredata-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b20932f5eef4544ff8ae6c2a483ea6d9d4e7f36d27520ec4f3c9c8dc47d92889", size = 16490, upload-time = "2025-10-21T08:02:29.7Z" }, + { url = "https://files.pythonhosted.org/packages/2f/42/afc082fcdc6229ce3246308e9d1ab401d3f07907f551827a3df76ea2507b/pyobjc_framework_coredata-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:c85318310737c3bf835fb6e4b5bf9bb333a7ac8b25a3880ea4a81adee8aa5852", size = 16643, upload-time = "2025-10-21T08:02:31.942Z" }, +] + +[[package]] +name = "pyobjc-framework-corehaptics" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8d/3a/040fc7a9dfebe59825cf71749d1085cdbd21a2b9192efbe0333407d7c2e4/pyobjc_framework_corehaptics-12.0.tar.gz", hash = "sha256:f2de5699473162421522347a090285f5394da7fd23da5008c1f18229678d84bf", size = 22150, upload-time = "2025-10-21T08:31:29.333Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/f0/928ebf2bae947ead0cf9aba49ad6f1085c4fa6c183e75d6719539348d2fe/pyobjc_framework_corehaptics-12.0-py2.py3-none-any.whl", hash = "sha256:b04d1a7895b7c56371971bc87aacbb604bb3778896cab3d81d97caef4e89240a", size = 5390, upload-time = "2025-10-21T08:02:33.396Z" }, +] + +[[package]] +name = "pyobjc-framework-corelocation" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/3a/a196c403b4f911905a5886374054019f3842873cf517f38c728905e0fe55/pyobjc_framework_corelocation-12.0.tar.gz", hash = "sha256:20a6fe17709f17ddbf9dd833a1a0ef045ad2e5838ba777f20eb329ed71c597c6", size = 53900, upload-time = "2025-10-21T08:31:33.838Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/8b/7b08d006d1eb8e44605657434a2f17e7fd16c87eef834081bb323ffca90f/pyobjc_framework_corelocation-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7417d38bf3ec97c14e87f7fedd8c4a978c27789fe738f15b774eb959dbbbe60", size = 12711, upload-time = "2025-10-21T08:02:37.466Z" }, + { url = "https://files.pythonhosted.org/packages/54/f1/9dd04add550c24953ac6a9845734f22100bf10a2d5dc20949ff7630ce239/pyobjc_framework_corelocation-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dbe100fa108b1b1fa4cd240953988ba4f0e1e60fa6402d8a45c715048675828", size = 12727, upload-time = "2025-10-21T08:02:39.31Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7e/415ebfe90b909a9400755702a49c985cd8dd8a0669dac7747eb289a703b3/pyobjc_framework_corelocation-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4b3480a4dc2b2dadea40513d3aea48137be418fb0603a50adbb10b277c654195", size = 12744, upload-time = "2025-10-21T08:02:41.228Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ab/7d27db51f524bdfd2714d1132d0105fb6fc35beff381ed72d2cace7ac4c7/pyobjc_framework_corelocation-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6ae6031dc633780b8ebdb50642891cd12221809a9da2314aa02949df108d8dee", size = 12880, upload-time = "2025-10-21T08:02:43.084Z" }, + { url = "https://files.pythonhosted.org/packages/13/ba/1a5e6b2efe67bfcffe1b919173ce1a410df4e48b7a85fd451511ea587998/pyobjc_framework_corelocation-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2c5c0ad450f18a22e800f50c3884652fce408ab0011e4d6c04c3f379056541d2", size = 12730, upload-time = "2025-10-21T08:02:44.88Z" }, + { url = "https://files.pythonhosted.org/packages/d5/5b/146f329a0fdb8f33b1eea712c40924f4ee39b8a3fef5e19d4a0bd044a8a3/pyobjc_framework_corelocation-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e182e340ceb24a3907afbd75745b0f50e25f3f85adc589f48521009c0ba9351c", size = 12876, upload-time = "2025-10-21T08:02:46.781Z" }, +] + +[[package]] +name = "pyobjc-framework-coremedia" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/6d/ed4f8b525a0520e609cea57fd0677bf7792e168297ad5577df1088eb7cd6/pyobjc_framework_coremedia-12.0.tar.gz", hash = "sha256:d7f76d2eb2890be9f8836b95682e83fa7f158c92043958daa71845fbc4a01ba9", size = 89928, upload-time = "2025-10-21T08:31:40.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/1c/5e5fe69b142c98b844803a0579cbd8ea555d1bfeecede95a918e58bdfb67/pyobjc_framework_coremedia-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed5684c764e1d4eab10cfd8dcaea82b598a85d7757cef35d36e6c78a4bd4b1e5", size = 29508, upload-time = "2025-10-21T08:02:53.135Z" }, + { url = "https://files.pythonhosted.org/packages/ec/15/9853b2e75db0bf47a80412f9584a84966310e3168dabde8d43f2c6fa9ff1/pyobjc_framework_coremedia-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:06e824c97391cacacfe6be4b80acdcb6924a8087d03d9af35ea0edf502f2ada1", size = 29406, upload-time = "2025-10-21T08:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/eb/08/15d500b9325f8c22ed379dba21559dfa9c7430c9b7eb709a55e515648c8d/pyobjc_framework_coremedia-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4d10a8b551626ae99a67436de498fc06a0beaa66db065baed19d7dfc5f1db44f", size = 29425, upload-time = "2025-10-21T08:02:58.756Z" }, + { url = "https://files.pythonhosted.org/packages/a9/98/ccf63a3a3aff8fce8be57b8ae1a67c9872e278a890c0508e86ed6bf98055/pyobjc_framework_coremedia-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:370aece067a0fb85e54eed57c6ca84118a55e7ff697988e5c82358d1bd3b648a", size = 29486, upload-time = "2025-10-21T08:03:01.687Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fb/43b2a78ffdcb1eed7f04c317f9675d40dcd573f805d7385fec6c54005a2d/pyobjc_framework_coremedia-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c89ca9d7cedd7b37178e358c83332933fcd65d82c362244aa208383724dce6f", size = 29462, upload-time = "2025-10-21T08:03:04.537Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2b/3cb4ba97483987b6dd9165e2da0f5e85f81044bd8fba26c409271dc2c880/pyobjc_framework_coremedia-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:aaa904d82f75f1e38a1ba8ba9a19a5acb3869304626b12fd6b60040a85188211", size = 29512, upload-time = "2025-10-21T08:03:07.678Z" }, +] + +[[package]] +name = "pyobjc-framework-coremediaio" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/4f/903bcf45358beda6efa5c926f66cb8ebe2b4345ea29e17b63c57bb828a28/pyobjc_framework_coremediaio-12.0.tar.gz", hash = "sha256:4067639c463df36831f12a5a87366700e68de054ea2624ee5695c660fe667551", size = 51467, upload-time = "2025-10-21T08:31:44.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/da/34a72c9dddb2651d3e2cf1c0c1d3c9981f721995d9ef6f8338a824c30a08/pyobjc_framework_coremediaio-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4c2dc9cc924927623c5688481106ad75a75c857f4444e37aaced614a69c2d52a", size = 17229, upload-time = "2025-10-21T08:03:12.881Z" }, + { url = "https://files.pythonhosted.org/packages/1a/01/b486563d03379c7d98d43b93a318c9af8aaded9d7d0b7e4f2c3d9e35ce0d/pyobjc_framework_coremediaio-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aa3e482ec13391f9f7a34529ee8b438ac241446bbfd81fbda48e46624beb1d39", size = 17285, upload-time = "2025-10-21T08:03:15.008Z" }, + { url = "https://files.pythonhosted.org/packages/44/82/7d7c0dd5987eabea2ee48a00909446b9332627d296f9874c567dc3c4e8a1/pyobjc_framework_coremediaio-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:768a2ec70927f9c74d0aa209f0051d1e7ce61d976a0bac519b1e380540d0a421", size = 17254, upload-time = "2025-10-21T08:03:17.159Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1b/3859742412f7659b666112ac50cabc29cd6909597713fbcedf2549b38d08/pyobjc_framework_coremediaio-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c6a1baae9dcf1731b0da312b6137a063a309a0d63688ae3f40a4bb78fecd1ce4", size = 17580, upload-time = "2025-10-21T08:03:19.266Z" }, + { url = "https://files.pythonhosted.org/packages/ef/84/144acef5ea102b8ad22a0078fbc3f8532b681ffc787cc46ecae192d0fc07/pyobjc_framework_coremediaio-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9af3c8e3523379ea7b50326cafada8ad7bf6d1881bd1e0f1ee1c0dbbbea057df", size = 17273, upload-time = "2025-10-21T08:03:21.673Z" }, + { url = "https://files.pythonhosted.org/packages/bb/1d/f87c421a35d3a10e52967511707acec81c1a942c2789a2bf5e7f46e71121/pyobjc_framework_coremediaio-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8e751b5fcfb66ff80bba8d9eea0210513326d3aaec519369c1c7601121b47b87", size = 17570, upload-time = "2025-10-21T08:03:24.134Z" }, +] + +[[package]] +name = "pyobjc-framework-coremidi" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/e5/705bc151fd4ee430288aaffcbaa965747b4c49564c2e2dcfa44e1208a783/pyobjc_framework_coremidi-12.0.tar.gz", hash = "sha256:0021e76c795e98fe17cefb6eb5b9a312c573ac65e7e732569af0932e9bc4a8c9", size = 55918, upload-time = "2025-10-21T08:31:49.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/63/33a66b10725bf5599a5c656fc5295e9e03ced21474b5fe06854df6af4ce1/pyobjc_framework_coremidi-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a67befca6b6b90afb3b4517c647baa7ef0e091d0856bae7fea2594e90fcaf12a", size = 24296, upload-time = "2025-10-21T08:03:30.107Z" }, + { url = "https://files.pythonhosted.org/packages/1f/dd/81ff166cdd0ec93af1090da2f166ac17abba9d56da456b9a442c4aefa01b/pyobjc_framework_coremidi-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:daca81f33444a8e7c910018826c53430ccad78270562bbe59ddbc9ec3a41b2f9", size = 24318, upload-time = "2025-10-21T08:03:32.683Z" }, + { url = "https://files.pythonhosted.org/packages/90/95/700498d0ce9f88a50ea5b0bf3be7d5dac6741f5003ac7f005306131c959e/pyobjc_framework_coremidi-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:731f8c5fd37d3c8117dfd27688d0cef70716f188ed763570532df3e74ce62b17", size = 24347, upload-time = "2025-10-21T08:03:35.101Z" }, + { url = "https://files.pythonhosted.org/packages/28/64/3e8eca8b1ea58e7adbb1a1e5a4e3532137920eb5b8257e362eee39718cea/pyobjc_framework_coremidi-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:78acecbfb811050a6bb41f77b23c037c1cbefd3df7aacb20caf1048b7065219e", size = 24502, upload-time = "2025-10-21T08:03:38.043Z" }, + { url = "https://files.pythonhosted.org/packages/84/55/0f21117eb6410865171f6407b824128206f2fd3a428c4b509fce4571c136/pyobjc_framework_coremidi-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:dd2f6ad40d5a39005aa4f0475e07002f4231f212a95b1f69ae10c81a39593563", size = 24384, upload-time = "2025-10-21T08:03:40.589Z" }, + { url = "https://files.pythonhosted.org/packages/62/89/6760795cc834055fce7c00d988fdf421c13e13e665979fd1f173e3187d79/pyobjc_framework_coremidi-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:05752f8d2739fdbc410f30c06689c321650d6238514faf47f84ef3d9ebc8556c", size = 24546, upload-time = "2025-10-21T08:03:43.453Z" }, +] + +[[package]] +name = "pyobjc-framework-coreml" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0c/a0/875b5174794c984df60944be54df0282945f8bae4a606fbafa0c6b717ddd/pyobjc_framework_coreml-12.0.tar.gz", hash = "sha256:e1d7a9812886150881c86000fba885cb15201352c75fb286bd9e3a1819b5a4d5", size = 40814, upload-time = "2025-10-21T08:31:53.83Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/3e/00e55a82f71da860b784ab19f06927af2e2f0e705ce57529239005b5cd7a/pyobjc_framework_coreml-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:410fa327fc5ba347ac6168c3f7a188f36c1c6966bef6b46f12543e8c4c9c26d9", size = 11344, upload-time = "2025-10-21T08:03:47.707Z" }, + { url = "https://files.pythonhosted.org/packages/09/86/b13dc7bed8ea3261d827be31d5239dbd234ca11fc4050f0a5a0dcbff97b9/pyobjc_framework_coreml-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:901a6343aabd1c1e8f2904abb35fe32d4335783ddec9be96279668b53ac0f4f9", size = 11366, upload-time = "2025-10-21T08:03:49.507Z" }, + { url = "https://files.pythonhosted.org/packages/57/41/b532645812eed1fab1e1d296d972ff62c4a21ccb6f134784070b94b16a27/pyobjc_framework_coreml-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:67b69e035559cc04915c8463c7942b1b2ca0016f0c3044f16558730f4b69782e", size = 11386, upload-time = "2025-10-21T08:03:51.645Z" }, + { url = "https://files.pythonhosted.org/packages/a8/df/5f250afd2e1a844956327d50200f3721a7c9b21d21b33a490512a54282b1/pyobjc_framework_coreml-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:75cf48d7555ec88dff51de1a5c471976fe601edc0a184ece79c2bcce976cd06a", size = 11613, upload-time = "2025-10-21T08:03:53.411Z" }, + { url = "https://files.pythonhosted.org/packages/b2/a8/d7d45503e569658375465242118092934fd33a9325f71583fdcbbc109cdb/pyobjc_framework_coreml-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5c6ebfa62e62b154ea6aa3079578bf6cf22130137024e8ea316eb8fcde1c22ae", size = 11426, upload-time = "2025-10-21T08:03:55.536Z" }, + { url = "https://files.pythonhosted.org/packages/08/93/30ab85521034cf65b9914a6e419e25ca8c55b43a5f4c69ee2a03c001b765/pyobjc_framework_coreml-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1e481ff8195721557eb357af8080c0ad77727d3fb6744a1bfa371a2a2b0603eb", size = 11609, upload-time = "2025-10-21T08:03:57.308Z" }, +] + +[[package]] +name = "pyobjc-framework-coremotion" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/85/15/d4bff65f1817a4be08c8dc572e40afb561394f6b98833cc1bd0799939fe4/pyobjc_framework_coremotion-12.0.tar.gz", hash = "sha256:7db1f7a5d1a29c631e000bdcf3500af9cc9d51eb140326ab8dc4aea0f4ea358a", size = 34231, upload-time = "2025-10-21T08:31:56.821Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8f/82/377885eb18ef3da482cfc35b7c0b45494669d320e00d3ff568dd9110e7f4/pyobjc_framework_coremotion-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d88f0733f9038741d77bceb920989e36f93c594b66b7f227afeca58d863b561", size = 10392, upload-time = "2025-10-21T08:04:00.976Z" }, + { url = "https://files.pythonhosted.org/packages/64/c3/3b8857e6b8dbc40bdb1f8943d5b2e76c6cd212fe9133b9936b19ac243894/pyobjc_framework_coremotion-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70e573b9f12d1817e56696c681b6a1146bb417934fa680ca309a29f6fb337129", size = 10410, upload-time = "2025-10-21T08:04:02.606Z" }, + { url = "https://files.pythonhosted.org/packages/b5/21/2238b5d8c092140f305bdaa41e1876950bb00664c06dfc6cef66123fa418/pyobjc_framework_coremotion-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fbfa46a5a81d7e1aa424011b56c6153b4e83ed34a81aab98f4432aeda469f4f0", size = 10428, upload-time = "2025-10-21T08:04:04.595Z" }, + { url = "https://files.pythonhosted.org/packages/4a/c3/2a3288ef1762ec800b1cb6beac0a45604d23eb1b4932a9294417b0f04769/pyobjc_framework_coremotion-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0c8675abf26b6a647b3a085cceb35fde938f07068085b3f9ea029f08cb4fa86c", size = 10570, upload-time = "2025-10-21T08:04:06.221Z" }, + { url = "https://files.pythonhosted.org/packages/0f/0d/abe75b17ddfbeb439d15e7c0f1cf6b5154520abdc95b286d613412d472eb/pyobjc_framework_coremotion-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:db0fa44ed782c3d5e76cb87bd2dc3a5c04cc0a8675520f0ed8a05b2aceab5d20", size = 10496, upload-time = "2025-10-21T08:04:07.851Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a0/4c2fdc40a6a3aa19fb624b9128851d6faf2b62bf226a534e94496af138a2/pyobjc_framework_coremotion-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2896ac44348c19d5e86f7892b5e843efaa7dd2dabba0527e9030bc482e1f11d8", size = 10641, upload-time = "2025-10-21T08:04:09.515Z" }, +] + +[[package]] +name = "pyobjc-framework-coreservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-fsevents" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/8e/e9ad1d201482036d528a9d9f18459706013f8e0f44a61b029d3164167584/pyobjc_framework_coreservices-12.0.tar.gz", hash = "sha256:36e0cb684d20c2ace81fde9829fd972a69463c51800fc1102a28118bfb804a0b", size = 366603, upload-time = "2025-10-21T08:32:20.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/77/01a822a4f287a161a434e09d4abafcefd112f70f44193fdd1c85fac9a835/pyobjc_framework_coreservices-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:323c6facd66684c71b5df1cd911f4fe3a468218e83ed14c21be4e7f6c787e9a6", size = 30204, upload-time = "2025-10-21T08:04:15.938Z" }, + { url = "https://files.pythonhosted.org/packages/06/4d/3c6f173c3f7a70f372936e26d14efbfd8300f12f8234f2d49566115e470a/pyobjc_framework_coreservices-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4ad1642efdaca73d607d4910f0cfd2137e1c54ac0d0fa183bb4a0db91ffd164d", size = 30214, upload-time = "2025-10-21T08:04:18.858Z" }, + { url = "https://files.pythonhosted.org/packages/18/89/e0a0799f1a4a55b837c944d755e66e11bf501126567871de1e8b7cf645ee/pyobjc_framework_coreservices-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad53b603762138ad88faec98fb27019ffb9083fce410b41225d8b41940e696d7", size = 30232, upload-time = "2025-10-21T08:04:21.852Z" }, + { url = "https://files.pythonhosted.org/packages/28/7f/db3b852ad49329e291ebbd8013de787ac2680eac1c7c5df80134d4ffe81d/pyobjc_framework_coreservices-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7f630bbdd99e3f980b5b256357097a54fc17acab442e6c16d76504d95d9adf0b", size = 30238, upload-time = "2025-10-21T08:04:24.836Z" }, + { url = "https://files.pythonhosted.org/packages/d7/4a/77310cb6e38ee2d7163ed962434c5ed528cb864b31e73020ded04f40c31c/pyobjc_framework_coreservices-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ee52df5f5dbf5a8b207e6c2319babe2766c4458fb3709b0d5e537a6394ff2c1b", size = 30264, upload-time = "2025-10-21T08:04:28.538Z" }, + { url = "https://files.pythonhosted.org/packages/81/68/f0b673b73368561a09e14e049f6d78ea595813af55d119fdf35c70432014/pyobjc_framework_coreservices-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6f745ced27f61b729042138db04601104b51d5569029595e801e0c27e0fde960", size = 30273, upload-time = "2025-10-21T08:04:31.696Z" }, +] + +[[package]] +name = "pyobjc-framework-corespotlight" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/18/7e/6f7cd71fb6795eba72a5886b3de8a3ec2c3ae6f1696340d6e51076d48eaf/pyobjc_framework_corespotlight-12.0.tar.gz", hash = "sha256:440181b5bb177ed76cea6e5d65ed39814b04f51bcfa02fba1b58fb5dc30d17c9", size = 38429, upload-time = "2025-10-21T08:32:24.56Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/fb/9a85e9c52b8fe75446f99faf9093555aa0198666051c9ddfb41a66fab6f8/pyobjc_framework_corespotlight-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1f5e2b003bd6bd6ece11f2d7366f11eef39decd79b2fcc4ef4624cce340a32b6", size = 9988, upload-time = "2025-10-21T08:04:35.511Z" }, + { url = "https://files.pythonhosted.org/packages/b3/f1/1b972471d0e3587cb25567a260c46d3a1f631549a60b2616f8d39b2f9bf5/pyobjc_framework_corespotlight-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ec1868b8387759668dfcb5dabe4a4458da8ee1da00b3c52388d80d1591fb7bd", size = 10005, upload-time = "2025-10-21T08:04:37.515Z" }, + { url = "https://files.pythonhosted.org/packages/f0/73/50db0cb816a1d47a77dfc998e1ba0e4159090438f465b96ecc10445183bf/pyobjc_framework_corespotlight-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7fb8b38bd6413b3fdcba4e5c710165835c84d0ea69800c5e8d5c8244286f9007", size = 10021, upload-time = "2025-10-21T08:04:39.1Z" }, + { url = "https://files.pythonhosted.org/packages/cf/d3/8e7e39111978edea5e3061b007e1cb1f199a019e0877d0d1dc37cffcdc14/pyobjc_framework_corespotlight-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:243e6d8b667402cd19dd9ec5402d33d5d761601d0c3ceea6de5b2e492f643d2c", size = 10163, upload-time = "2025-10-21T08:04:41.106Z" }, + { url = "https://files.pythonhosted.org/packages/2e/de/0eabeb3ec532658ac0b13c4802802555d09fed23a47ae9243cda9142d556/pyobjc_framework_corespotlight-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9d63fa40c2fee8de6ae6aa687d6110cd9b2faeeb0459930e5a73add0fe3dc2b3", size = 10081, upload-time = "2025-10-21T08:04:42.73Z" }, + { url = "https://files.pythonhosted.org/packages/cd/94/a9d0e3fa2b2fdde4df51fb5047ad91f89224f1b2499bcb23c7e70725caa5/pyobjc_framework_corespotlight-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3c9e2a61a5bf6399fae62c3f0cf3ac2f024752b5153aa47844cdbdfbafc63cac", size = 10219, upload-time = "2025-10-21T08:04:44.468Z" }, +] + +[[package]] +name = "pyobjc-framework-coretext" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/36/32ec183e555b73152d7813f6f7c277fd018440f70a1f142bd75b04946089/pyobjc_framework_coretext-12.0.tar.gz", hash = "sha256:8cc0c7dd2b7e68ad1c760784e422722550c77cbdbd60eb455170ec444ca1cfd2", size = 90546, upload-time = "2025-10-21T08:32:31.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/b2/55fd3dce67223e799d862a62f2b8228836e3921dbf58a2fba939ecf605e1/pyobjc_framework_coretext-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:681b6276e1b14b79a8de2ba25dd2406fa88b147a55775e19bf0a2dd32f23c143", size = 30001, upload-time = "2025-10-21T08:04:51.101Z" }, + { url = "https://files.pythonhosted.org/packages/40/7e/146d609f67784b184f9d0d178d57be4f9e0542ea73201c2f0d5a6d4341b2/pyobjc_framework_coretext-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a17dfb9366ce16be7da3d42c14e67bcd230a90cafada2249110e237e8ce1d114", size = 30118, upload-time = "2025-10-21T08:04:54.428Z" }, + { url = "https://files.pythonhosted.org/packages/30/64/31da2b1236c710b963510fc03008ebe607d03e2c0288467db9bf9f297873/pyobjc_framework_coretext-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecd424cf6da1a69cad40ef4007bc5af842ccb7456c5fcc4c9aded40e3e0c22ba", size = 30119, upload-time = "2025-10-21T08:04:57.402Z" }, + { url = "https://files.pythonhosted.org/packages/21/1d/d23fa47ffb6ad32e26a58e357619b5564b4f6e421a839d12961cce521c8f/pyobjc_framework_coretext-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:60e84e46e0aeb12101a4354c39ce84066107773b0c96fdc4ff15fd1662dc88d8", size = 30702, upload-time = "2025-10-21T08:05:00.387Z" }, + { url = "https://files.pythonhosted.org/packages/07/e4/96caefd91817d0f82aaae089e4421cbbef2a216933b5c98435ee2927fbef/pyobjc_framework_coretext-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6ecf89af6de87072f1615fb89d7ed51b345000850a9b827774f262bf6be5acac", size = 30104, upload-time = "2025-10-21T08:05:03.331Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b5/9152c1a2d8a6fb06d48a36d95b5bb919e820a2f623ca8313ab5eba263be0/pyobjc_framework_coretext-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ddc34c91d16a653db81963141d29f8fc82550fc7a39ed39ff0332764d844ffe1", size = 30714, upload-time = "2025-10-21T08:05:07.092Z" }, +] + +[[package]] +name = "pyobjc-framework-corewlan" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/06/ed26dab70dce1e2137e08cd18beca9313bccb2cc357bcbf5764c776b85ff/pyobjc_framework_corewlan-12.0.tar.gz", hash = "sha256:a724959e0b9b0fcc7b698b7c0a6e8457b82828c3a88385c9ac8c758791aed15a", size = 32760, upload-time = "2025-10-21T08:32:34.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/9b/24bbc483ea6471d3d9321f3e768cd5399c5d41ab7a700a81114b120bd89d/pyobjc_framework_corewlan-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d9180f71c2169c8530c3592b5ab8809fbc93ed1d3526e26443fe927784aad259", size = 9942, upload-time = "2025-10-21T08:05:10.538Z" }, + { url = "https://files.pythonhosted.org/packages/d4/13/50e3c6fee0ae19d502ae9c42cee3da28a7b86a476abe59082f9403e43ef8/pyobjc_framework_corewlan-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82bbe5e172d99d47070cc4ad9715306df789fe97031da0af3b25f084f8e47586", size = 9964, upload-time = "2025-10-21T08:05:12.129Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1c/f4bcb0c6cdf1cc5184f266aecf814ca60e4acbb3b65bfa9395d39fb0f425/pyobjc_framework_corewlan-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1bf43f273f5bce60dd60c98739bd5877581f04027774018549d8ffd81a3f93ea", size = 9974, upload-time = "2025-10-21T08:05:13.731Z" }, + { url = "https://files.pythonhosted.org/packages/ac/9f/53e0886d9fe5de867cf77c0e0c6f90b8b40058375c3bf3770fe878e5aae9/pyobjc_framework_corewlan-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7f2c0d38dc39877365185dd748c5e61ae5c418dec5b2683cebedd653d1a333e6", size = 10124, upload-time = "2025-10-21T08:05:15.727Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b1/7043bab71e3f917711ba4da5f7ac8a248fe6a6f56dfaacf12f739de097a4/pyobjc_framework_corewlan-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:e8675a8aa5906d22cfd6ccc834344ddfd6527a362c0c140e4659f349a59c9915", size = 10016, upload-time = "2025-10-21T08:05:17.371Z" }, + { url = "https://files.pythonhosted.org/packages/39/4b/7f4c8d26b7c9f1389ee075f44f123b5354046dc2b8f884b6ecf66a734128/pyobjc_framework_corewlan-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:85958c9e61c6894ff6f039b771f5b01a9f53a8ad4d930504bfe1c1c2dfdef1e9", size = 10177, upload-time = "2025-10-21T08:05:18.986Z" }, +] + +[[package]] +name = "pyobjc-framework-cryptotokenkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/31141f2f8ba250d1de21895984b179ca2307870a5c00e97f0ad34227303c/pyobjc_framework_cryptotokenkit-12.0.tar.gz", hash = "sha256:3b6aa22c584a5e330be6c85ca588798686c7eb3e25f06e069c12e82eacb36c38", size = 33086, upload-time = "2025-10-21T08:32:37.683Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/5e/488baba13dc3dc3b66ff009e492436f81c4282e038070950ac7c46f3d9e1/pyobjc_framework_cryptotokenkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bacf606c2a322fa3d7d9bfc0a9ae653a85450308073ff19d3e09b3c6b4bd1c2a", size = 12605, upload-time = "2025-10-21T08:05:22.903Z" }, + { url = "https://files.pythonhosted.org/packages/b9/16/b3809fb5959fe33aae4c463ae2c82398ad71499278d2114341bd57c7dcd2/pyobjc_framework_cryptotokenkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b130e3769439076458ca6e9f5e337b99d38cdc47c2d4d251513efacc99fcf26", size = 12643, upload-time = "2025-10-21T08:05:24.832Z" }, + { url = "https://files.pythonhosted.org/packages/21/f3/016fa856ae44547273ed36c2d87a4ae7376b9eda6dfaa80e3515ed853f42/pyobjc_framework_cryptotokenkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0907f65b48857ed1724299aca5fa94f96abb56cc078d7455e7ba4dbcf1dee77d", size = 12660, upload-time = "2025-10-21T08:05:27.853Z" }, + { url = "https://files.pythonhosted.org/packages/fb/56/7e2bd25abd3ee53ff98765615850393851408033d13d1a2dc0796e7236ff/pyobjc_framework_cryptotokenkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:973efe489fff55b7e688bf62c161c18c0007d8b029f09d80267a1181a8aca6f2", size = 12845, upload-time = "2025-10-21T08:05:29.746Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7d/112a3b8308fa18e65b86b9d2f09cc3e00758df6a24b96f0776ba8e008274/pyobjc_framework_cryptotokenkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d58427f8794250574a4ed8736efd294414755ecbd84bc103531aeeaaa5b922ee", size = 12639, upload-time = "2025-10-21T08:05:31.969Z" }, + { url = "https://files.pythonhosted.org/packages/4a/f7/6132d386f89a013d87bd210da86e66182e0dc5942f309c6122baa79e5931/pyobjc_framework_cryptotokenkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0bafe8ca98d016637b9ae94b845469e6fd193922a004194dd75c5e8768fff718", size = 12848, upload-time = "2025-10-21T08:05:33.73Z" }, +] + +[[package]] +name = "pyobjc-framework-datadetection" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/a1/2d556dd61c05f8fdd05d3383eb85f49d037cb3ccc276da10d38c86259720/pyobjc_framework_datadetection-12.0.tar.gz", hash = "sha256:3784ce6f220dc1bd7bc39fed240431500f106d4ae627ff2b99575ef7667f2a37", size = 12377, upload-time = "2025-10-21T08:32:39.458Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/1d/5fa176aa5734c99ed0c99c64b547225ac97f6254ce00703d13289f09b4f2/pyobjc_framework_datadetection-12.0-py2.py3-none-any.whl", hash = "sha256:6715d68cb38a3660e083fb8c70bce75c30e61d91cd7818f006b6e2cb49491e05", size = 3505, upload-time = "2025-10-21T08:05:35.095Z" }, +] + +[[package]] +name = "pyobjc-framework-devicecheck" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/56/72626225f821c6c7aef0bb14100e5418b9c4a46c101236336096e9f9b2ad/pyobjc_framework_devicecheck-12.0.tar.gz", hash = "sha256:dc51a4ac7afb68f7dbfaa6ec74b85ac0915058be9d4ee5e17b2ca33edde57d28", size = 12953, upload-time = "2025-10-21T08:32:41.158Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/31/ee708c5f5329da63ad4448eed9079c4310c140a0d064cce9a03bb8c112e4/pyobjc_framework_devicecheck-12.0-py2.py3-none-any.whl", hash = "sha256:b11efc8d82875de368cd102aedea468da32fed6d0686b5da2eeed9cd750cc5ae", size = 3696, upload-time = "2025-10-21T08:05:36.564Z" }, +] + +[[package]] +name = "pyobjc-framework-devicediscoveryextension" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/b4/7fd6b558a657d1557ce41be0f647473f739079a6f5e1289cdd788fb717e0/pyobjc_framework_devicediscoveryextension-12.0.tar.gz", hash = "sha256:77a6a39468a9aa01d127b14ea314870b757280ddd802e7b30274ffc138b7a76c", size = 14768, upload-time = "2025-10-21T08:32:43.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/a5/b48b9018ebaf3d79ed01c33ba23828a2c10ad276f45457c7b5dd0b00ecd7/pyobjc_framework_devicediscoveryextension-12.0-py2.py3-none-any.whl", hash = "sha256:46c1a39be20183776ee95cc7b2132e2e3013aeea559ec0431275a77a613c4012", size = 4327, upload-time = "2025-10-21T08:05:38.142Z" }, +] + +[[package]] +name = "pyobjc-framework-dictionaryservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/14/18a56b54e3fe6477f6a9ab92a318f05fd70b0b7797f4170bcd38418aba37/pyobjc_framework_dictionaryservices-12.0.tar.gz", hash = "sha256:e415dcdcc93ab42bc7beaab9b6696f6c417e57ace689d3e7d7ed9b1fef5d1119", size = 10589, upload-time = "2025-10-21T08:32:44.649Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/b0/c57721118d28a9cd3d05fb74774c72eb2304b95a2a7beb1d7653fdd551e6/pyobjc_framework_dictionaryservices-12.0-py2.py3-none-any.whl", hash = "sha256:f8f54b290772c36081d38dfc089d5ed5c4486a7a584a7e1f685203e1c8b210f6", size = 3940, upload-time = "2025-10-21T08:05:39.627Z" }, +] + +[[package]] +name = "pyobjc-framework-discrecording" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8c/ab/a6126d2a23e50cb5c53a731a4eb084b98c9ee7fc86ba3952a61ef1729c39/pyobjc_framework_discrecording-12.0.tar.gz", hash = "sha256:cb2bc1c9ea9c4f3ed38e4fa64ed0d7ff3c1d8cfa2a90cee5680e9468190aeb17", size = 55974, upload-time = "2025-10-21T08:32:49.274Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/fb/946cdb1c70df944d5fd6e28c300f15c8672c4ef74f30b4a578deba09749c/pyobjc_framework_discrecording-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ece9ff8b81c6ca1ab1360e7052346dfffa752f494edbe701d25f2312629f084", size = 14560, upload-time = "2025-10-21T08:05:43.902Z" }, + { url = "https://files.pythonhosted.org/packages/b7/1f/ac20e19df780b7d14a7ae741da672400c5c8d331c41ab014ea025517ae2f/pyobjc_framework_discrecording-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:817ed6254bb81e4703e6841c474025ca281a242a9f09f274a02f66128a4c6b6d", size = 14567, upload-time = "2025-10-21T08:05:45.802Z" }, + { url = "https://files.pythonhosted.org/packages/f2/5f/ec63dda83d0616c68855801e4c3aa341b9c47b9d6cecbbcce57f26e637aa/pyobjc_framework_discrecording-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d5e3f5ac73ee969ee99a12057ce6356609971f52a2323b1b5f1abb7ba5fcee50", size = 14582, upload-time = "2025-10-21T08:05:47.824Z" }, + { url = "https://files.pythonhosted.org/packages/96/50/d844de9cb36193dc990fd68ac7989e9f592fd8d50971bcd1a71b4d0815d2/pyobjc_framework_discrecording-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:91e369ff415c189df373a4e435456eb227e2579636801b4635cd60577293d06a", size = 14756, upload-time = "2025-10-21T08:05:49.84Z" }, + { url = "https://files.pythonhosted.org/packages/ac/bd/56b912a9a1314696b9e5d23e99632601689f9e2ff8a08a17214f761ecbaa/pyobjc_framework_discrecording-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:6f62d945627c78acfd5ffd523e86a5d4ae41cfcd0c2683e437ee9e65aefccb5d", size = 14646, upload-time = "2025-10-21T08:05:51.834Z" }, + { url = "https://files.pythonhosted.org/packages/d2/85/cb54cc0344900c4bc34e3eb02ada9dae5a966b5ec4bd733490f781b45429/pyobjc_framework_discrecording-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:95f09e2c715660fff406637946a4b8d7696dafd2c3c00d840c46b15fede91667", size = 14818, upload-time = "2025-10-21T08:05:53.829Z" }, +] + +[[package]] +name = "pyobjc-framework-discrecordingui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-discrecording" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/12/895107bac87ad78c822debb9c68bfc17d7e632f9778cfb8f01b3b7fcafc8/pyobjc_framework_discrecordingui-12.0.tar.gz", hash = "sha256:31d31a903f4d12753e24e77951fe1fc2e27a7bf8643e7b97ba061d41008336ec", size = 16477, upload-time = "2025-10-21T08:32:51.288Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/ce/35f69d7fb296e7548d2d76de446e02c351890a745799454e85bd170c60ca/pyobjc_framework_discrecordingui-12.0-py2.py3-none-any.whl", hash = "sha256:3cce85f3d13f28561e734b61facc1a16b632b73e69c5f14943816cf0fa184cdc", size = 4716, upload-time = "2025-10-21T08:05:55.284Z" }, +] + +[[package]] +name = "pyobjc-framework-diskarbitration" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/96/be0ced457c9483efa7ec9789abcd5945446bc54ab1d785363c5f8d8bbd45/pyobjc_framework_diskarbitration-12.0.tar.gz", hash = "sha256:88df934c0cbc63daa496e2318e9ffa1d5e0096b6107fcff550afdd6817142813", size = 17191, upload-time = "2025-10-21T08:32:53.577Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/9c/79e41d6fedea3c07d1a9d83b1d6ad2585a0d9693b57a8b92ee60a0c19135/pyobjc_framework_diskarbitration-12.0-py2.py3-none-any.whl", hash = "sha256:690e34ea7548c21519855e5d1ebb0fcf9538d7562ec15779c5c63b580d9c855f", size = 4889, upload-time = "2025-10-21T08:05:56.835Z" }, +] + +[[package]] +name = "pyobjc-framework-dvdplayback" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/28/a9b7a2722cf94382ec843601e656524246384f3ff710a60c18e617acc756/pyobjc_framework_dvdplayback-12.0.tar.gz", hash = "sha256:433e8790641a210304b47079965eda2737578033747f3eb20d1758afcfbb35a2", size = 32345, upload-time = "2025-10-21T08:32:56.597Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/81/57fe080195079c27e45bcfbc528895549f6f35080fb41dde6720485964ec/pyobjc_framework_dvdplayback-12.0-py2.py3-none-any.whl", hash = "sha256:9d68ed25523e14faf6c79f89d87c21942147063b7e5cb625edad40e9dffe6360", size = 8253, upload-time = "2025-10-21T08:05:58.852Z" }, +] + +[[package]] +name = "pyobjc-framework-eventkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/c4/b6e30b7917777bb74d3caffb6568e4644c0b9cfa75b0dfc4942bfde3fad1/pyobjc_framework_eventkit-12.0.tar.gz", hash = "sha256:6a67a70cee1d9399cca2c04303ec10ae0d2a99ceca1bd7f9a3c67ff166057680", size = 28578, upload-time = "2025-10-21T08:32:59.228Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1c/49/aa23695c867aafea7254058218202bffda0abf1b3bbf2d1c617a73266662/pyobjc_framework_eventkit-12.0-py2.py3-none-any.whl", hash = "sha256:1771062ab40d26e878cbf27bdf1f9fe539854c62eea8b44d7be9218dc7d6ce67", size = 6827, upload-time = "2025-10-21T08:06:00.692Z" }, +] + +[[package]] +name = "pyobjc-framework-exceptionhandling" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/51/e6/afbd7407d43562878cf66f16bc79439616a447900f1dadf5015e9bbf3f8d/pyobjc_framework_exceptionhandling-12.0.tar.gz", hash = "sha256:047dc74c185b9bacb165a6d77a079a0ccec099f0ab516da726273305e41b18f6", size = 16748, upload-time = "2025-10-21T08:33:01.159Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/c3/97804dc40a8a3af7a01b71b52a50bb2d43e4bb6aabb15a20de083f49caa6/pyobjc_framework_exceptionhandling-12.0-py2.py3-none-any.whl", hash = "sha256:d69f34caf50bd2fe135d04ffc00342e4b1c0d76340170418688317ad4685ac08", size = 7124, upload-time = "2025-10-21T08:06:02.731Z" }, +] + +[[package]] +name = "pyobjc-framework-executionpolicy" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/40/10c3c6a10d0b2829e96fcf3f8375846e5af1926b9b024147c9fc7e0ceff8/pyobjc_framework_executionpolicy-12.0.tar.gz", hash = "sha256:508d1ac045f9f2747db1a93ce45381f4e5f64881f4adc79fb0474f4dbe6237eb", size = 12649, upload-time = "2025-10-21T08:33:03.053Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/67/b8398c778e3821f666d8530974e216f7e7c148beb5fa0088c151935b6554/pyobjc_framework_executionpolicy-12.0-py2.py3-none-any.whl", hash = "sha256:6b882acdbfe5cc6f0783f9f99ffb98d2d34eb72b0761e8cc812f7b518b77b2a8", size = 3749, upload-time = "2025-10-21T08:06:04.194Z" }, +] + +[[package]] +name = "pyobjc-framework-extensionkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/54/36ea7f32481e5e4cc1bac159ff9e4dc94fd4827f544e85caa2a03b4c5938/pyobjc_framework_extensionkit-12.0.tar.gz", hash = "sha256:02e6b5613797a79c77b277b352441c8667117b657b06b862277c681d75cc7c01", size = 19085, upload-time = "2025-10-21T08:33:05.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/a2/4a280fc8c6df72b6a3ea83997251fd8bdc81c06cb09fc726b2d2c1000613/pyobjc_framework_extensionkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:83c4adb2a6dcc45666c08f0d9cfc9a6021786dfb247defea5366d0cdccb03544", size = 7924, upload-time = "2025-10-21T08:06:08.124Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/1f66656b0514189192d867d1937321d5aedcadaae796702f58299a922ddc/pyobjc_framework_extensionkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9d5c95e090b08594e4fb7e57c3cbfc30a6058c9504e908beebb97a963126e6dc", size = 7941, upload-time = "2025-10-21T08:06:10.047Z" }, + { url = "https://files.pythonhosted.org/packages/08/ef/a4fe3c097e55244f27ade55af62e5a8a747fc87c2285b6838ec2c1593550/pyobjc_framework_extensionkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f0d037a5288d709ea6eb44adf5406d324958f693aca622b840078d8a5825db2", size = 7950, upload-time = "2025-10-21T08:06:11.815Z" }, + { url = "https://files.pythonhosted.org/packages/67/6c/8a2b08eaa67c883eb434821af0d415168dd7123fcbf3e03ad7bb4bc3cd27/pyobjc_framework_extensionkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:eed6b5bf85b9d06c5e47b95c3b36fd530b3c768cda830b58734ba18cdd5b39ba", size = 8099, upload-time = "2025-10-21T08:06:13.703Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a2/df77539dd30d5344f223a4fc5bc9414ae8029ba5b196cdf7a33d6f6cffdb/pyobjc_framework_extensionkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2c3dc04387cf96467e3aa8221150b6d0ed9d52af26980ff3eca012671eb662df", size = 8018, upload-time = "2025-10-21T08:06:15.464Z" }, + { url = "https://files.pythonhosted.org/packages/9b/f3/764fe0feb220667b85110d95399e76d567a4d626ed2ae7d1eabc0c685c2c/pyobjc_framework_extensionkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1a97ae6663bd5faf256484fcbc85625cb9735994fcce83d0bfa912967b33e3df", size = 8157, upload-time = "2025-10-21T08:06:17.023Z" }, +] + +[[package]] +name = "pyobjc-framework-externalaccessory" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/77/af/65fb12b47da17c7cbe32c5650fbe6071aa7ca580d1db27f6760730bbba55/pyobjc_framework_externalaccessory-12.0.tar.gz", hash = "sha256:654301eb0370eef57ddd472c8e71e25a0f0e6d720e38730369b1c3712fe67b0b", size = 21353, upload-time = "2025-10-21T08:33:07.688Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/7a/d90b0e09d784e18c5a3ea1530d234c225de758cb8bb24cb4e6882e8c9736/pyobjc_framework_externalaccessory-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:913b0e5ef1047ad87b6b5e690ac3dd7132f25c51874ba4552a57092d161374ab", size = 8919, upload-time = "2025-10-21T08:06:22.259Z" }, + { url = "https://files.pythonhosted.org/packages/eb/e8/e40ebad20df2d4124e701a08d7d421091d42c8465681f7578cb03b233ab3/pyobjc_framework_externalaccessory-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:281fd839361e48a2b193f4cb3b4690d9551de31a6b2fd12a8bdec085cf835b26", size = 8937, upload-time = "2025-10-21T08:06:23.921Z" }, + { url = "https://files.pythonhosted.org/packages/37/00/56c302c594516fd9cb1e64c073774ba1e3337a1236cd55a88d5ef0f2acee/pyobjc_framework_externalaccessory-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2e8ea60aede93ed6af3b121f95aedfffe87913659ee470d9140eedaf3cac04d7", size = 8953, upload-time = "2025-10-21T08:06:25.53Z" }, + { url = "https://files.pythonhosted.org/packages/8d/2b/74456a9f89e966560e09beb4841bd8ee52284f2eb6692e0cce3adebba343/pyobjc_framework_externalaccessory-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b9275d656f44464b96e75cb1d5514ef6806747ca3d9e34469d409a8bd16eaa22", size = 9111, upload-time = "2025-10-21T08:06:27.168Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fa/c647e023dafc79675024f5a0afa9ea179a7c97ae9d6a267129cf541857f6/pyobjc_framework_externalaccessory-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f2e188740640270af2b608682bb041b9006d38899657c54d775acc723ba7c7ef", size = 9009, upload-time = "2025-10-21T08:06:28.837Z" }, + { url = "https://files.pythonhosted.org/packages/f8/61/a9cdaf3bca459b81a8f4d2d367eb9753ee7ebbd56733588ddf1bf0e95e25/pyobjc_framework_externalaccessory-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:38f655c538a6a7dc65ff83b6fb2c6d9441f9334612012fc2c05d3e7f2f9f2073", size = 9193, upload-time = "2025-10-21T08:06:30.778Z" }, +] + +[[package]] +name = "pyobjc-framework-fileprovider" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/3c/57bcedb1076903d44078ecfa402ee4a27a3cee123a86e684c8683316b2d1/pyobjc_framework_fileprovider-12.0.tar.gz", hash = "sha256:8b0c33f34c123b757b09406e6fd29a8e5b3348cc8e271533386af860f2bfce65", size = 43431, upload-time = "2025-10-21T08:33:11.66Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/3b/0a439219ec7f71bad775481d4f943c1ac8eebe3d841938160049cbf55cb6/pyobjc_framework_fileprovider-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd2a7b6d79e3dd1487375c0f9a653b0242d5abe000915d443cc57ab384369f64", size = 20981, upload-time = "2025-10-21T08:06:35.412Z" }, + { url = "https://files.pythonhosted.org/packages/9d/54/9c4e41fe4a2c9eb91c1d4cf3501d4d3843f40ee5ab9fbc9ecf4202ef0f42/pyobjc_framework_fileprovider-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:14db02897901a02eca7c7a1e587bc3fb89eb72f7d53c30a8f449c53768275501", size = 21019, upload-time = "2025-10-21T08:06:37.756Z" }, + { url = "https://files.pythonhosted.org/packages/34/38/401a24b91f299bc7de29e9ec61c214ae4b84d6834f629fb34858d34fe7e0/pyobjc_framework_fileprovider-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b4fcea703e8f8b17b0503b7b48c071bef524f5420f5ae4c66fcd35cf87a85bb", size = 21016, upload-time = "2025-10-21T08:06:40.082Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c4/43325b4d2161ea22180087bf29f3c784cdc22ed2c395ee6324a123bcab4f/pyobjc_framework_fileprovider-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:dab249a72005cd473bf18cc5d335bacac15bf9faeb639960d7b38594543f6a45", size = 21307, upload-time = "2025-10-21T08:06:43.218Z" }, + { url = "https://files.pythonhosted.org/packages/f6/7d/6f7cd199ce73c6b0001cbaf972531ca64f90c405e2362a776cee8614cb81/pyobjc_framework_fileprovider-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f6b842ea2f9bc7fab2bfc8bf62262a4e4594b7b29052afc4587dc1bb601507ba", size = 21066, upload-time = "2025-10-21T08:06:45.473Z" }, + { url = "https://files.pythonhosted.org/packages/eb/51/571806793ef91f8c522a879a24b621b816f777ebe39b9e0f0f625d219a42/pyobjc_framework_fileprovider-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:040e13cb5ec00bc9453bbed2fe65b8b8900c035cf169cc76e6c4fd96760a683d", size = 21343, upload-time = "2025-10-21T08:06:48.227Z" }, +] + +[[package]] +name = "pyobjc-framework-fileproviderui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-fileprovider" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/19/fb3a1ce592110c02152b1663ce82ec9505af9310dc1b4d30b6669e2becdb/pyobjc_framework_fileproviderui-12.0.tar.gz", hash = "sha256:7d6903eeb9a1b890d26d4beff0fa027be780c2135eab6a642fbfdcad71dfa78c", size = 12476, upload-time = "2025-10-21T08:33:13.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/24/41981f2d97c7beeaf7b48351fc7044293f99ffd678c5690e24e356ce02f4/pyobjc_framework_fileproviderui-12.0-py2.py3-none-any.whl", hash = "sha256:821e5a84f6c2122cd03d64428a9b0af2d41ee27bce8b417d9fa7a97470a97ee7", size = 3723, upload-time = "2025-10-21T08:06:49.631Z" }, +] + +[[package]] +name = "pyobjc-framework-findersync" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/8f/7574edd92f3ba6358b14708ab40a049d2a4c02029ac6f4f88f498074a0ba/pyobjc_framework_findersync-12.0.tar.gz", hash = "sha256:7a7220395127bec31b4cbbbe40c1ec8fa0f5586c241e5c158c567543338d766d", size = 13615, upload-time = "2025-10-21T08:33:15.282Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/93/b49eb8f4e8bdc8892018acfd82b0be9b5b4f2cc44416867bf3afa0e16ccc/pyobjc_framework_findersync-12.0-py2.py3-none-any.whl", hash = "sha256:0b27ef0255a04d0241700bd68d30df629c01a02afeb9ab2aad0bd50219022485", size = 4901, upload-time = "2025-10-21T08:06:51.271Z" }, +] + +[[package]] +name = "pyobjc-framework-fsevents" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/2b/52f6c1f1c8725b08d53c8fe4c0ea18fb17a91674b8023e20d6aef0f15820/pyobjc_framework_fsevents-12.0.tar.gz", hash = "sha256:768bfc90da3547516b6833e33f28d5f49238c2b47f44b8a9b7c941b951488cd9", size = 26890, upload-time = "2025-10-21T08:33:18.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/de/77ba26869434b6af5261a8da3d60633fa7529335e73efb46f6a8799c1f0e/pyobjc_framework_fsevents-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:72107b82442e644b603306ee65900cc5a25a941b3374c77c0f3c3db713cd442c", size = 13070, upload-time = "2025-10-21T08:06:55.91Z" }, + { url = "https://files.pythonhosted.org/packages/b3/d2/2f47bf12ab314f3f792ea70616cbd9be01d03de2a4ae7df104aa519e9871/pyobjc_framework_fsevents-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b48c86d919ad554b6a8aee0e6536ed3877425d4eaa83b9e9ad1cc52482c15123", size = 13154, upload-time = "2025-10-21T08:06:58.089Z" }, + { url = "https://files.pythonhosted.org/packages/af/ab/085b9012909b7daee172c0466d25f38928b9c8d905da0d8b8a2e85aeb81a/pyobjc_framework_fsevents-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0fdddf5a11b2d3f46d75e53d72aa01dedb74bbbcdc0251df4e47196989f1102e", size = 13155, upload-time = "2025-10-21T08:06:59.986Z" }, + { url = "https://files.pythonhosted.org/packages/df/7d/5ea57bf2a101c37a019bf2a2af3c1444c85aa6602d5aab52630c8d470237/pyobjc_framework_fsevents-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2bcfc084dfc4db42f503eeecb5d3e8f5cad9cf54f14ab84e61f6d24c41276454", size = 13518, upload-time = "2025-10-21T08:07:01.996Z" }, + { url = "https://files.pythonhosted.org/packages/d9/1d/3105e4419e184e1b31ededdd788c5f2a9c9b97cfa0a391f584218cc8ec85/pyobjc_framework_fsevents-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a066a7f3aa2eb9e1cdae0773939a736e133fbdaf08a36b07558cf9283f9c5541", size = 13047, upload-time = "2025-10-21T08:07:04.186Z" }, + { url = "https://files.pythonhosted.org/packages/1e/70/feb81655ed49ef3b4adc211e98cbc9f0360a380deb74afaeb8f4cf064519/pyobjc_framework_fsevents-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a8046f4cecaa5b107bd1968a99925bbccf36ef9ab70e9ac6990483334465967a", size = 13510, upload-time = "2025-10-21T08:07:06.124Z" }, +] + +[[package]] +name = "pyobjc-framework-fskit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/6e/240f3ff4e1b6c51ddb48f0ebb7dfb25d6d328b474fc43891fbbd70a7e760/pyobjc_framework_fskit-12.0.tar.gz", hash = "sha256:90efb6c61aa27f7a0c7a9c09d465f5dac65ccfc35753e772be0394274fbad499", size = 42767, upload-time = "2025-10-21T08:33:21.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/1b/7d33b5645ab26f51a0e69c19649880021c6e45176bb9cf52df5f41703103/pyobjc_framework_fskit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:decb8b41bed5a66f0ee7d4786a93bf81a965edd2775e6850ad5d30af374e8364", size = 20234, upload-time = "2025-10-21T08:07:11.223Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b2/4317f6786a2b0b0050378bf07a0ed09b613d1f3a8917aa6e9b2e5bd8ab80/pyobjc_framework_fskit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:75fbc58f0e7f2fbbb3fb0ac4e8338c238e363a0fffe0efc369badb832d690c2a", size = 20254, upload-time = "2025-10-21T08:07:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/82/7d/95b2effe20b05f8b99cc85838ab25c1da09d8ba5d80ae91a9d02c5a89942/pyobjc_framework_fskit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6b56bb27d6e628594c09fe61d7de42b4c63499fa402b2b486669a904519aea4c", size = 20265, upload-time = "2025-10-21T08:07:15.879Z" }, + { url = "https://files.pythonhosted.org/packages/73/a6/341008b04ac28924e5e1e1c038f117e22e2edab11741941eb34a3d45db87/pyobjc_framework_fskit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be56f2edc7f25dbf94cc579f84bd33bdf0278f742a95565cb5ae8a2305fba774", size = 20497, upload-time = "2025-10-21T08:07:18.223Z" }, + { url = "https://files.pythonhosted.org/packages/70/c4/7e9fbbc5ab1e349f700e870fae04a67f6a9c58e5456cf3e93c4b397be2e0/pyobjc_framework_fskit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fb228d94776a7b8e73259302231fc0c9db2423d404e75fafc867e637b740f4a9", size = 20300, upload-time = "2025-10-21T08:07:20.394Z" }, + { url = "https://files.pythonhosted.org/packages/b9/ea/fa33ebef6388bce4533bb5892638ff1b6dd571229ebb1e6b99bca363e3b4/pyobjc_framework_fskit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ae6a2c2c9dd0ba405f1c9cdc4dd63c22e713257baa73ae394dacaa84066b8ed4", size = 20546, upload-time = "2025-10-21T08:07:22.658Z" }, +] + +[[package]] +name = "pyobjc-framework-gamecenter" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/46/f4a7d4aef99e82a65a6c769cf5eed4dad42c8a9a6b2bc72234590513990f/pyobjc_framework_gamecenter-12.0.tar.gz", hash = "sha256:c33467f4a8d93b1d6d3e719d6d11d373909ede6e86f61eaf5fa936d8d7e78cdf", size = 31860, upload-time = "2025-10-21T08:33:25.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a3/0a/8b38d1d2ce1866ad6236d26762cc9ad75191381f151d917a8ec14de3c6c1/pyobjc_framework_gamecenter-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0e2307e623f97228e3880c8315e9f5b536fbc0f78bba36197888e56c1286c7dc", size = 18829, upload-time = "2025-10-21T08:07:27.153Z" }, + { url = "https://files.pythonhosted.org/packages/33/78/d363c9865329e66022b7cd97f965b3785008e13ec6a7ef075c4a56499c97/pyobjc_framework_gamecenter-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba76966392c0e29168cdd651fce17b64d356718f5630feae028c702db5d8139a", size = 18872, upload-time = "2025-10-21T08:07:29.645Z" }, + { url = "https://files.pythonhosted.org/packages/07/47/2c589fd453099d326bc077e7dff19ca41e9b68fc006ebe289a0724cd4dc8/pyobjc_framework_gamecenter-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:74633c2460344f44e88adff0e1c46a76622ea6b957dcd6959f2b930a99cd72ef", size = 18876, upload-time = "2025-10-21T08:07:32.798Z" }, + { url = "https://files.pythonhosted.org/packages/1b/de/c21fc23b087dc399546dc82fd6cc0492eeb51990e7a4ff58bc65cfa1231c/pyobjc_framework_gamecenter-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d2091f1ba0703b2119163853e490d9c90c014194510155be58ea3eab8629473c", size = 19166, upload-time = "2025-10-21T08:07:35.006Z" }, + { url = "https://files.pythonhosted.org/packages/50/5b/02252fcba11bcf20e4c772d60c2500a2f432c3bb1019f37a56152e438e16/pyobjc_framework_gamecenter-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a1375778604896b13d9b84ae93053db2cf052376ad9c63fc16431ef2211150d1", size = 18932, upload-time = "2025-10-21T08:07:37.491Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ea/fda2bc1a852688cb4866dc82d88532d28dc648182c3943c6c2f0654164f9/pyobjc_framework_gamecenter-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7f4c5073d52fe6d2ccf2a7ef5d39b283cd33c2f9357fc5d463abac66b77c3ac0", size = 19221, upload-time = "2025-10-21T08:07:39.685Z" }, +] + +[[package]] +name = "pyobjc-framework-gamecontroller" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/f2496dbe861fff298f6f7d40f2aff085d04704afd87320fcf11227397efd/pyobjc_framework_gamecontroller-12.0.tar.gz", hash = "sha256:d01ede48c35ae62b27db500218a7c83b80a876c0ec2ac42c365f9b8e711fc8e2", size = 54982, upload-time = "2025-10-21T08:33:29.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/06/5023f57029180f625c2f7c837c826a61a49a9aa0088e154f343e64a3a957/pyobjc_framework_gamecontroller-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c1eadf51b2cfd9aed746d90e8d2d4eded32d3f6a06f5459daa4a1fd65ebd96fa", size = 20918, upload-time = "2025-10-21T08:07:44.73Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c3/de3bf0e6f2ad7a25cbb6cac65d7f9b21cc0369c2761204d17a97b8535a77/pyobjc_framework_gamecontroller-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c09715ca3d4cf8f6ff51f7f9d98c22c790368d3c5cfbe6461fd0b393ccf73d4", size = 20954, upload-time = "2025-10-21T08:07:47.618Z" }, + { url = "https://files.pythonhosted.org/packages/39/30/0d7e4c08e2f43c3c5a741619d3c3101c977e30a31fe4e1ce759c38711eeb/pyobjc_framework_gamecontroller-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:39f5381980247367b659f2d468df63223b11c8d9f43d11231a291d86b8a3aea9", size = 20963, upload-time = "2025-10-21T08:07:50.26Z" }, + { url = "https://files.pythonhosted.org/packages/c7/54/5069dbbb9b84e88254a6ac28b6ed9e43e1df4319909375730dc9838652b2/pyobjc_framework_gamecontroller-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:163ecc202b1a43e4e4331a23eff3a5404834b6415cd4380fc5f8288daae00d4e", size = 21232, upload-time = "2025-10-21T08:07:53.003Z" }, + { url = "https://files.pythonhosted.org/packages/af/3a/18c8bd006aad3b67ae822cb66370fbb0268b58127777190016a2bdb3196b/pyobjc_framework_gamecontroller-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:2108d420e876cf324270f179d27df58b116cc22a95afee9975ad5fe589a2ea77", size = 21010, upload-time = "2025-10-21T08:07:55.66Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c1/d70e32b6add228de574e03fa9477bddac8706329b319a8d3e8b45e6400a6/pyobjc_framework_gamecontroller-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7ee5b5bfaf9f8a4ae7357902b04e2aa8c1fdc6f66cb867464dfc4d06a64a1de1", size = 21278, upload-time = "2025-10-21T08:07:58.102Z" }, +] + +[[package]] +name = "pyobjc-framework-gamekit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/aa/2734bdd000970d8884a77714c5adebba684c982821f9293205e2cb71b429/pyobjc_framework_gamekit-12.0.tar.gz", hash = "sha256:381724769aa57428eefdb11f1fae9cf6933061723a5806ac41dc63553850f18c", size = 64236, upload-time = "2025-10-21T08:33:34.51Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/b1/6c5a4a147605bb6563c35487fa08bdb9ce9fa6223ed8bfe6df9af277c973/pyobjc_framework_gamekit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:21f13014588ff9f1e9c680ff602d50f021a25017825e6101a53be15ea27a547e", size = 22468, upload-time = "2025-10-21T08:08:04.598Z" }, + { url = "https://files.pythonhosted.org/packages/ff/03/7e0571f56c394e148207af9b1e1e158927f42095b189cd7b231948178206/pyobjc_framework_gamekit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:981b7009964949076b64aeb2c467127c789cfa0377a5637352431188613f0a15", size = 22496, upload-time = "2025-10-21T08:08:07.462Z" }, + { url = "https://files.pythonhosted.org/packages/42/07/f442ace3c1bee84e5f317f57d375f101b59e5d932033272320b8e4a725ac/pyobjc_framework_gamekit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4f4a4b58ebf5986c941a98c828431cad9495f5483041605dd5f114c628212519", size = 22513, upload-time = "2025-10-21T08:08:10.236Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ae/6b2901d9c360648c5ad61b72d74eda8b512d6da77226fa87c5a62af3168b/pyobjc_framework_gamekit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:28fdb8992ec926f67159700637495cca0271519c278e22f410fb65260404df6c", size = 22805, upload-time = "2025-10-21T08:08:12.754Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d2/5d413a8cccd68cb5aa8a10f461aa426f3d93dfb39204e632063f71ba66c5/pyobjc_framework_gamekit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:08414996660aa25f86fe4583649f702769a9600ba5bd5c37152e1bee36904df5", size = 22545, upload-time = "2025-10-21T08:08:15.306Z" }, + { url = "https://files.pythonhosted.org/packages/04/f8/58b74fd7b4f321d6d028754fc50effa90b9b2161af2a26d3641fb9b192f5/pyobjc_framework_gamekit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:604ca75774845f99b0781290319b642db6e95810275423cc7f1bb1bfbef72295", size = 22859, upload-time = "2025-10-21T08:08:17.829Z" }, +] + +[[package]] +name = "pyobjc-framework-gameplaykit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-spritekit" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/d9/d506dde3818c09295f11af52176cf3a6a5d00333cea19069ff44c44a4a89/pyobjc_framework_gameplaykit-12.0.tar.gz", hash = "sha256:e0ff1cac933f5686b62c06766fca7e740932d93fb7e1367e18ab3be082a810dc", size = 41918, upload-time = "2025-10-21T08:33:38.116Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/31/03e40bc9896c367f08cf220f740e47225beaeca35d4845abe98e67cb5b12/pyobjc_framework_gameplaykit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ca24ed4b4f791751799c25b8288b498c2702e9b2d38ee8884ef10f9da96d2f0", size = 13136, upload-time = "2025-10-21T08:08:22.412Z" }, + { url = "https://files.pythonhosted.org/packages/fb/83/37bcc458ec68c0ea36e8151f0f2859f936fe7b4bbd201c44434d7c52cdff/pyobjc_framework_gameplaykit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:35d08927d06f135f2d3149a5944095c0853624d27e011d52b318409b8ff0c080", size = 13161, upload-time = "2025-10-21T08:08:24.268Z" }, + { url = "https://files.pythonhosted.org/packages/33/88/3f4fa760b3acb2680bd3e165a68b130f447e9458f2ba9f75fd9aa7ab2023/pyobjc_framework_gameplaykit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:41b865b484fa885dc5fe26621c599f9a81ab36a8076a23955c73ca2d1a912b15", size = 13174, upload-time = "2025-10-21T08:08:26.136Z" }, + { url = "https://files.pythonhosted.org/packages/93/66/1fcbc04b3e48d3843fcbd53486a9fe072da7560c7b3089c48cc35a1bd97a/pyobjc_framework_gameplaykit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2d4a3fb37cb4393f7bda1e9ced78f7a83962b49c846c3357b768cad7a111b841", size = 13389, upload-time = "2025-10-21T08:08:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/42/30/ab2f6c35603b01f4ef7409c6f850d13cd6323d2c24e87e73c60320f922cd/pyobjc_framework_gameplaykit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:74fdd8a02deefbbb000ed614a859b153df45245c35d4a27e7e8194f2c7532501", size = 13179, upload-time = "2025-10-21T08:08:30.263Z" }, + { url = "https://files.pythonhosted.org/packages/53/7c/e2753b7dbf88249f3147b8b14da9aac335b0d93ea12015b1b2f10a9490ba/pyobjc_framework_gameplaykit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9515b9fc5f58d0e9331ec9c4df10e9ab2374556bf9957bf1fdba4d553cf8715d", size = 13375, upload-time = "2025-10-21T08:08:32.01Z" }, +] + +[[package]] +name = "pyobjc-framework-gamesave" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/b6/de69ddc08ea89a6e2dc3cb64b0ba468996b43b6d91e65463d66530f1cef6/pyobjc_framework_gamesave-12.0.tar.gz", hash = "sha256:2412a243b7a06afa08c46003bbe75790d8cfae2761f55187dd54b082da7ca62f", size = 12714, upload-time = "2025-10-21T08:33:40.191Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/84/27dab140da6102f23f1666630d876446152e1d28b35920e65797496d4222/pyobjc_framework_gamesave-12.0-py2.py3-none-any.whl", hash = "sha256:a5be943b5969848b44d2132e33ed88720aa4c389916e41f909e3a7a144ea71cf", size = 3697, upload-time = "2025-10-21T08:08:33.335Z" }, +] + +[[package]] +name = "pyobjc-framework-healthkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/24/8c/12fa3d73598d80f2ce77bc0ab1a344e89fd8b5db93a36c74e1c925cf632a/pyobjc_framework_healthkit-12.0.tar.gz", hash = "sha256:4e47b84ed39f322e90a45d39eb91ddcde9fffbf76c75b6e700b80258db3ec58b", size = 92173, upload-time = "2025-10-21T08:33:46.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/c0/915497d4e19c07ac14d36fb9ca333b79dc7f7309bac056e143defdeaee35/pyobjc_framework_healthkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b16f091a36a4606023e7f69758406bb08c2c66d8157ae04f011e3e054d0d4ea", size = 20797, upload-time = "2025-10-21T08:08:38.665Z" }, + { url = "https://files.pythonhosted.org/packages/96/4e/d2a43c2d09cda2e514ee0837ff0cd86caaa876cfd9ee6afd03ba180ecd4d/pyobjc_framework_healthkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb437fcbde6d622cca1c6735acdf10922e0098aa7266487e1504bb93225992ba", size = 20804, upload-time = "2025-10-21T08:08:41.044Z" }, + { url = "https://files.pythonhosted.org/packages/7f/bd/369f2a1adad473cbe15942f81d829a21fee04af69a21aa23937405c10173/pyobjc_framework_healthkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:75d1aa170b3d2b0d6f0ad91f8fa9426765a86a7a747d4cdf4aec7714cce90c3e", size = 20822, upload-time = "2025-10-21T08:08:43.331Z" }, + { url = "https://files.pythonhosted.org/packages/75/45/fba110652b41849cd96080b35f94482be4b232236c6f309125a77dadc6ac/pyobjc_framework_healthkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c8f59013b88da01ea677cce7e58d5885bf6663397f531ec18693466b968403a7", size = 20991, upload-time = "2025-10-21T08:08:45.565Z" }, + { url = "https://files.pythonhosted.org/packages/49/8f/6810a866a73d92163dd998c1a2dd67b76df54ff943c1a138137815e36c6f/pyobjc_framework_healthkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:078563e7fc5a4f492ea972b1d86b5b10ec20484bfb798e18c92c7c6ef252697d", size = 20878, upload-time = "2025-10-21T08:08:47.845Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/fdb299a61f6bad7b2d0b73197c2f9ff9fc5f4e6544ab445dee4b823debae/pyobjc_framework_healthkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4680698a20a3baa869bb2a96a14a5588453518ffa83abf67c72a404ff91e94ee", size = 21055, upload-time = "2025-10-21T08:08:50.113Z" }, +] + +[[package]] +name = "pyobjc-framework-imagecapturecore" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/a7/52fa4a0092feaa2c0b72256b3593e03028a8e491344e64c074bdbf33d926/pyobjc_framework_imagecapturecore-12.0.tar.gz", hash = "sha256:36d12a818660de257635b338f286083d09a5b34e4ebd3bc6aae4b979028585cd", size = 46807, upload-time = "2025-10-21T08:33:51.102Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/0d/8fc4d7fe9f2bb48748355c7ab87a2e12acfbc715f6a9fadec57ed1e854aa/pyobjc_framework_imagecapturecore-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:42610501ebd9671c11a2dddbb06501fe2c79b35536c90d0854eb543568d4f259", size = 15993, upload-time = "2025-10-21T08:08:54.39Z" }, + { url = "https://files.pythonhosted.org/packages/1b/55/5984ba8122f3b703d1460b4a73e4aba0c6997b82bfc160458c62a88e1015/pyobjc_framework_imagecapturecore-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:26483df9fdb63d156642471c9031d75720cae654efbb4f264ebe96f532913290", size = 16020, upload-time = "2025-10-21T08:08:56.419Z" }, + { url = "https://files.pythonhosted.org/packages/51/94/acb74f94acf23ea16ff28b7d55e1872b4ae0c15b105bc49785c67caf5cac/pyobjc_framework_imagecapturecore-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f4889fbcc17948335e2be5dcaf40d171c6f7ea514bb9994dbb3519a4d6a0de5d", size = 16032, upload-time = "2025-10-21T08:08:58.63Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/51feaf4fd51624c3800d235fd70e791b245867296090d4b6d4675923e9a6/pyobjc_framework_imagecapturecore-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bd8d7db7a7acb97fa363e800fd47cf0d026db17fc635ff6c2306a0ba855ae6db", size = 16222, upload-time = "2025-10-21T08:09:00.741Z" }, + { url = "https://files.pythonhosted.org/packages/12/80/e4d7f1ef9664e8f01af06b0c025b77c7362ab319d8b20cf33a2700598b34/pyobjc_framework_imagecapturecore-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c94f3514bc9ed4288b0568f05b18cbc2138b7656c51e18316a7334f29a472b97", size = 16029, upload-time = "2025-10-21T08:09:02.748Z" }, + { url = "https://files.pythonhosted.org/packages/e3/0a/13ffde2aa24224f93ed7cba6381b58fb7312475c6e871ee5cdf393be3541/pyobjc_framework_imagecapturecore-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e04142bec0c3b042c12efafe8948458ff22ef63cd8622cdb13fa84912ac99e2b", size = 16218, upload-time = "2025-10-21T08:09:05.093Z" }, +] + +[[package]] +name = "pyobjc-framework-inputmethodkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/49/c58dc9dd9dfce812cadcafb1da8bed88af88fe6f10978a0522ab4b96ceb5/pyobjc_framework_inputmethodkit-12.0.tar.gz", hash = "sha256:a5c16a003f0a08e7ac005a6c4d43074bb5e4cf587d5e57a4f11c47232349962d", size = 23449, upload-time = "2025-10-21T08:33:53.964Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/36/7b8be5c8202cb3e184542dd72dcee00cf446ecc14327851630cd4cf30db3/pyobjc_framework_inputmethodkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95194c1df58d683cf677eb160c134140e93e398c43b9c0d03b0e764f9cf79544", size = 9512, upload-time = "2025-10-21T08:09:08.825Z" }, + { url = "https://files.pythonhosted.org/packages/87/76/4e53c1f2519dda7b9ecc06c3dfb31711a07e08a4c543fccf51bbb82c842a/pyobjc_framework_inputmethodkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:171b6dcf88065cc50d7615f18ec90a9c3ade4298ec829c0cd64229b5d7674a2d", size = 9521, upload-time = "2025-10-21T08:09:10.477Z" }, + { url = "https://files.pythonhosted.org/packages/08/fd/c6237dbc593158edfa7993a51341009bdc3a0daa1c2d2fd191d6e9fbaad6/pyobjc_framework_inputmethodkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fff98e1ba95f5f4ef69d59e791820497498f72a53ef1abf561c819d933273bd7", size = 9533, upload-time = "2025-10-21T08:09:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/da/81/c4c2237988738a19015637053a288cc07eca452065e8430f0456f63c4047/pyobjc_framework_inputmethodkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5648177af6040ac5b9c1c12c35862b4a3ab8b7819b609b9543644deb6f7a7d62", size = 9700, upload-time = "2025-10-21T08:09:13.771Z" }, + { url = "https://files.pythonhosted.org/packages/6e/65/ff921650fa5647bb36cf5281f6c6b16fd3da1f0564360481f9b5a79a7516/pyobjc_framework_inputmethodkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:44f5dec871e2555fa82901d56506b200ec80556acbf7041588dfa8fdad5adfce", size = 9585, upload-time = "2025-10-21T08:09:15.397Z" }, + { url = "https://files.pythonhosted.org/packages/7e/89/87cf9de076846929f55f779333967987755c3d7d1caa15fa04f464032ff6/pyobjc_framework_inputmethodkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a8b8c3b00c07109923ac48e495ae610c970d7a9c6698b71c3697a5b47d42e985", size = 9757, upload-time = "2025-10-21T08:09:17.331Z" }, +] + +[[package]] +name = "pyobjc-framework-installerplugins" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/65/403d3d6244f8e85201b232b37aacde4d6e80895b7d709047ce71b3f5e830/pyobjc_framework_installerplugins-12.0.tar.gz", hash = "sha256:fbd5824e282f95999ae14b0128ad7bc3dad4b44a067016a8e3750f0252f4d6b7", size = 25313, upload-time = "2025-10-21T08:33:56.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/d5/be8217352ebb3d78b600bd85fe274f44f642fd8268b3bca4335caaa7da85/pyobjc_framework_installerplugins-12.0-py2.py3-none-any.whl", hash = "sha256:60950cc9dd4fd0f5e4e8d4cbcf3197765f20b390a8fbfd91478c955e6d90ba11", size = 4826, upload-time = "2025-10-21T08:09:18.707Z" }, +] + +[[package]] +name = "pyobjc-framework-instantmessage" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/30/e4/fe583666b7f99aa14d8656600823668d008f52ccce0476c0c9ab2d2ada46/pyobjc_framework_instantmessage-12.0.tar.gz", hash = "sha256:8a9fa19a03c6c56a4e366422259d46a5462ddee23acdb44e74f71e3f923e1aa5", size = 31255, upload-time = "2025-10-21T08:33:59.489Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/79/0e/0e768739befaffe849d1b3aaf2b7078c04d6b2b3e14fb37c53b44c09a291/pyobjc_framework_instantmessage-12.0-py2.py3-none-any.whl", hash = "sha256:9b0068f669e735f59b5d5ccb44861275530cb4bc4aca5e1fd7179828a23f500d", size = 5446, upload-time = "2025-10-21T08:09:20.334Z" }, +] + +[[package]] +name = "pyobjc-framework-intents" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/b6/d2692a8710a9c2c605f8449c90d38cb454ec5e4d35731a97beceed1051f2/pyobjc_framework_intents-12.0.tar.gz", hash = "sha256:77e778574911fe4db80256094260f959c60ad9d67f9cd3d34c136fc37700bba2", size = 132672, upload-time = "2025-10-21T08:34:08.981Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/4e/dcdcdfd8a09c9fa6cd2574ccc1475eedce832c7bfe2981d2c8a8e0eb7e09/pyobjc_framework_intents-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2b97a3bbf9dd987a0441028e58a0ba6a95772c41a72347f0c27ebd857e20225", size = 32144, upload-time = "2025-10-21T08:09:26.908Z" }, + { url = "https://files.pythonhosted.org/packages/88/c6/c705055cb7429adf418718722f051d407d702648eede2fcc85ed125e2994/pyobjc_framework_intents-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7cb3fb5f0877c6562cfd5189323c0eb2d7378bd8d67da01fc24b04e00a47bbea", size = 32166, upload-time = "2025-10-21T08:09:30.176Z" }, + { url = "https://files.pythonhosted.org/packages/0a/d5/e1561117512c7a29d98120362b9769aff4d1747f809053fa2c4973042257/pyobjc_framework_intents-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b5da926124f9e4171438bd19ce80caee6a53fd3cccfd6c1d61874bf24871558a", size = 32177, upload-time = "2025-10-21T08:09:33.824Z" }, + { url = "https://files.pythonhosted.org/packages/04/eb/467619274e835a8c0bcd39293f2bcfcf44bb34c35b9773669a37ababad0d/pyobjc_framework_intents-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7fc800d5055eed336d772bc3ddda92f50db6c9b11fe2c8225d1c1e35ca0d7f27", size = 32419, upload-time = "2025-10-21T08:09:37.423Z" }, + { url = "https://files.pythonhosted.org/packages/68/74/09f806440a5164ad2506b3acd6bf799d5a66ed2a09c4d808b8f980670588/pyobjc_framework_intents-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9c42a92611daad86ff5b1bce44d37f072ddabe06fc5e6084b676a5d380c501a6", size = 32200, upload-time = "2025-10-21T08:09:40.524Z" }, + { url = "https://files.pythonhosted.org/packages/93/0c/edbdd9d3b4f1160c95d4ef0fa27ecd3e87afb81748e1e84a7f2b0626815d/pyobjc_framework_intents-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:43093cab2ad5482314dde83c2ee88a90fccbc8a21942b0884ff18a6f4ce2bd6d", size = 32484, upload-time = "2025-10-21T08:09:43.605Z" }, +] + +[[package]] +name = "pyobjc-framework-intentsui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-intents" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/1c/ac36510c5697d930e5922ae70c141c34b0bd9185e1ca71f8de0a8a9025da/pyobjc_framework_intentsui-12.0.tar.gz", hash = "sha256:cb53f34abef6a96f1df12b34c682088578fbc3e1f63d0ee02e09f41f16fb54a8", size = 20142, upload-time = "2025-10-21T08:34:11.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/ea/cfd64403776dca3fa53ea268dc80a4840c83bc517a01cb4a9f29f6bea816/pyobjc_framework_intentsui-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a3f25724f442cb5f8113d7e4db15e612c27b8c6a7c68b0db8f2a27f16ac6ea04", size = 8971, upload-time = "2025-10-21T08:09:47.323Z" }, + { url = "https://files.pythonhosted.org/packages/0b/40/c6da25755b54cd86d2c01ae02235a2806f077ef1eaf2ebf6d783fdcaa3d3/pyobjc_framework_intentsui-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:43937d3f7b9acc8bd23b039bfe5c30e6d7ce5cb365603deb8b47dbccc18bb421", size = 8990, upload-time = "2025-10-21T08:09:48.949Z" }, + { url = "https://files.pythonhosted.org/packages/82/ee/f93c685c993c58c17d45a3dad9f57b0507756641a858d009c73f47865371/pyobjc_framework_intentsui-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e9e57aac35b9cd4b5e95fa9715a8a4a753c53f1747fe08f32c3701b270fc0d05", size = 9014, upload-time = "2025-10-21T08:09:50.58Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b4/59ad5706c4f40f26a912a587bccd82ed94e9a22da4c76fe7fc040058af2c/pyobjc_framework_intentsui-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d7b3d7b5609a2c605b5b80038af18a6cb042ba39a394d200ffbc3a3fe78e7473", size = 9184, upload-time = "2025-10-21T08:09:52.182Z" }, + { url = "https://files.pythonhosted.org/packages/56/5f/7b8035431b2bec046dc4ac672d9960c1cc23bc14dfbd01ad88c98a2891e6/pyobjc_framework_intentsui-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:5e1e5ed95f76135dd9662f7509aaac51b273e54d75a17dbc10e9872758cc1d8b", size = 9071, upload-time = "2025-10-21T08:09:55.106Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e5/e3ebdd7a4666482a26754fa7e4b5987d773af14a5aacc096dd6aaaaa5c6f/pyobjc_framework_intentsui-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f9a339d68a276513f5545a2a4446095921451193bb81157f00bc564e65392981", size = 9259, upload-time = "2025-10-21T08:09:56.699Z" }, +] + +[[package]] +name = "pyobjc-framework-iobluetooth" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7f/a2/639dd9503842ec12ecd2712b58baf47df96ca170651828a7dc8e7a721a9e/pyobjc_framework_iobluetooth-12.0.tar.gz", hash = "sha256:44eb58bab83172f0bba41928a5831a8aa852151485dc87252229f0542cecd7c8", size = 155642, upload-time = "2025-10-21T08:34:22.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/68/086ee6f5a4a0b6c59d9b2e2775252c6ba18853ecfc726e6f3095ddf285b8/pyobjc_framework_iobluetooth-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:921ae54acf5d823678686eb4945f6875f98146ebcdc4cb6a115468a73bb7864d", size = 40419, upload-time = "2025-10-21T08:10:04.061Z" }, + { url = "https://files.pythonhosted.org/packages/61/96/34547e64f74d381b9ee5f8840f81a3fc47884479cc0208b700e3ee09a0c1/pyobjc_framework_iobluetooth-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5239949754c2156f8bbc93f05a73639c514f9f5b3e72466886fda3de3b0fdb97", size = 40449, upload-time = "2025-10-21T08:10:08.411Z" }, + { url = "https://files.pythonhosted.org/packages/71/94/744b0dc6e0bd1e08dfa73e73966569f0a69300c0d9c6f9cdb7a4c21d96dd/pyobjc_framework_iobluetooth-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78591a41a9621e52c718c6b150058076a407099f9ba3092c8214c3b097cb2833", size = 40462, upload-time = "2025-10-21T08:10:12.082Z" }, + { url = "https://files.pythonhosted.org/packages/08/54/e64dc86f1582f347a9b20e1a2a468ee694a90ec84fb0758ea5b0dc21a807/pyobjc_framework_iobluetooth-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b42aedf60074b36b3d99cad743e45e070091d305880f4d14e87023a8a190a57c", size = 40674, upload-time = "2025-10-21T08:10:15.691Z" }, + { url = "https://files.pythonhosted.org/packages/49/b6/d6a5bf68337b8301ce1ed8bed3bee1c39f1c224a56728d02cd780b894041/pyobjc_framework_iobluetooth-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:91e5b230c1f0ffe803362c9b1512b5669b26838d31ff839b27e63e7ad0b76bc6", size = 40451, upload-time = "2025-10-21T08:10:19.291Z" }, + { url = "https://files.pythonhosted.org/packages/47/d0/6c80e57378fec38c0747c65ab5315d7d7f8d18ae2941d79b0ba57f9b58e9/pyobjc_framework_iobluetooth-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d0f666e9f9053c9cea55d64707c0365dbb4a05829656bbde5ccc9ff1d0e6356f", size = 40654, upload-time = "2025-10-21T08:10:22.996Z" }, +] + +[[package]] +name = "pyobjc-framework-iobluetoothui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-iobluetooth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/95/22588965d90ce13e9ac65d46b9c97379a9400336052663c3b8066f5b2c70/pyobjc_framework_iobluetoothui-12.0.tar.gz", hash = "sha256:a768e16ce112b3a01fbc324e9cb5976a1d908069df8aa0d2b77f0f6f56cd4ad6", size = 16536, upload-time = "2025-10-21T08:34:24.041Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/af/b6df402c5a82da4f1a6d1b97cf251a6b5c687256e7007201f42caeaa00f1/pyobjc_framework_iobluetoothui-12.0-py2.py3-none-any.whl", hash = "sha256:2bfb0bf3589db9b4a06132503d2998490d5f2ad56e2259fb066c05f19b71754a", size = 4056, upload-time = "2025-10-21T08:10:25.203Z" }, +] + +[[package]] +name = "pyobjc-framework-iosurface" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6e/8f/b4767fbf4ba4219d92d7c2ac2e48425342442f9ecea7adb351da6bc65da1/pyobjc_framework_iosurface-12.0.tar.gz", hash = "sha256:456a706e73e698494aec539e713341f6b1bd4c870c95a0e554fe0b8d32dfda06", size = 17739, upload-time = "2025-10-21T08:34:26.355Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/9c/e65b489d448ec26bf3567228788fb36931412719447c8e87002375de42b4/pyobjc_framework_iosurface-12.0-py2.py3-none-any.whl", hash = "sha256:734543a79f6bceb0ade88138f83657c23422c33f2b83f732d09581f54c486ae3", size = 4913, upload-time = "2025-10-21T08:10:26.678Z" }, +] + +[[package]] +name = "pyobjc-framework-ituneslibrary" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/94/d7f8ac73777323c01859136bf50ba6cfc674fc8c5eedb0aa45ad3fa6b4cd/pyobjc_framework_ituneslibrary-12.0.tar.gz", hash = "sha256:f859806281d7604e71ddbf2323daa853ccb83a3295f631cab106e93900383d57", size = 23745, upload-time = "2025-10-21T08:34:29.075Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/20/b5a88ab437898ba43be98634a3aa8418b8990c045821059fb199dbf6c550/pyobjc_framework_ituneslibrary-12.0-py2.py3-none-any.whl", hash = "sha256:7274a34ef8e3d51754c571af3a49d49a3c946abf30562e9f647f53626dbea5e2", size = 5220, upload-time = "2025-10-21T08:10:30.203Z" }, +] + +[[package]] +name = "pyobjc-framework-kernelmanagement" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/d8/54cdf0e439b71e11dd081dfbdc0c23fd9122a90deab2a819a9ef08b6abab/pyobjc_framework_kernelmanagement-12.0.tar.gz", hash = "sha256:f7fa54676777f525eda77c261a6f2120256855f28531fd18fd0081be869d003d", size = 11836, upload-time = "2025-10-21T08:34:30.812Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/26/57122ddbe123b20b02b3c0510fc80719507ac849e311479d47225c13f7c2/pyobjc_framework_kernelmanagement-12.0-py2.py3-none-any.whl", hash = "sha256:a7cc70a131dbd3eb8b0b22c5283baf9b6c52ecbf26a5c689c254984719b17049", size = 3712, upload-time = "2025-10-21T08:10:31.777Z" }, +] + +[[package]] +name = "pyobjc-framework-latentsemanticmapping" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/67/40a1c7d581a258f8dc436e3768f137d9c3885346f6f8aabcd35d9a472147/pyobjc_framework_latentsemanticmapping-12.0.tar.gz", hash = "sha256:737f2ceb84c85ab5352ad361f674c66be7602a5d2d68fbcfbe28400cf04fb1fa", size = 15564, upload-time = "2025-10-21T08:34:33.021Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/57/bc9764affff2e6b3cea4c3e8bf527fc70b2bba600f1f4d079a3ecfd2b090/pyobjc_framework_latentsemanticmapping-12.0-py2.py3-none-any.whl", hash = "sha256:de98fb922e209f16cbacdaf60c186893b384fda9077293dd74257ea118502780", size = 5483, upload-time = "2025-10-21T08:10:33.389Z" }, +] + +[[package]] +name = "pyobjc-framework-launchservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/a8/c93919c0e249f3453ea2e2732ea1b69e959ac50bf63d8bf87017a8def36c/pyobjc_framework_launchservices-12.0.tar.gz", hash = "sha256:8c162e7f021b8428a35989fb86bc6dfb251456ec18b6e7570a83b3c32a683438", size = 20500, upload-time = "2025-10-21T08:34:35.212Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/51/f249292cb459f25c3ea09cdee7b8faaeb9cd06d62a02e453f450c5015879/pyobjc_framework_launchservices-12.0-py2.py3-none-any.whl", hash = "sha256:e95d30f2f21eadfd815806f2183735d8c93ed960251ef9123850dcb1b62c9384", size = 3912, upload-time = "2025-10-21T08:10:35.19Z" }, +] + +[[package]] +name = "pyobjc-framework-libdispatch" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/7e/251ea268ce5a341586c963de758c7ff6dea681c98a1fb6da87f6d0004bd3/pyobjc_framework_libdispatch-12.0.tar.gz", hash = "sha256:2ef31c02670c377d9e2875e74053087b1d96b240d2fc8721cc4c665c05394b3a", size = 38599, upload-time = "2025-10-21T08:34:38.878Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/c2/7aff056399d9743a8c66af1ef575cf1741ce4c67c13c02d6510f0bd6151e/pyobjc_framework_libdispatch-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea093cd250105726aff61df189daa893e6f7bd43f8865bb6e612deeec233d374", size = 20472, upload-time = "2025-10-21T08:10:41.466Z" }, + { url = "https://files.pythonhosted.org/packages/50/4b/1bc4b4fef8beeb77eedf0c8d1e643330bcce42a4839e37f54105bcfc02a5/pyobjc_framework_libdispatch-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:631db409f99302f58aa97bb395f2220bd6b2676d6ef4621802f7abd7c23786e8", size = 15660, upload-time = "2025-10-21T08:10:43.752Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b3/e61f3b08e0145918a3e2a2f4450b4d3f3ac6eb251f923d0850a85a984053/pyobjc_framework_libdispatch-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:006d492b469b2a1fe3da7df9687f2638d832ef76333e5f7c6ab023bf25703fbf", size = 15681, upload-time = "2025-10-21T08:10:45.758Z" }, + { url = "https://files.pythonhosted.org/packages/64/e3/7befaf176f09ba2648bcf4506a458ca67379d0c61cdfd00d0cd0690ed394/pyobjc_framework_libdispatch-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:bb73f193fab434bd89b4d92d062a645b0068f6a3af50e00df3bc789f94927db6", size = 15948, upload-time = "2025-10-21T08:10:47.797Z" }, + { url = "https://files.pythonhosted.org/packages/a1/33/6db320381e215a1a772d3ed2d094680c1797faa22cec799e5086cb850e02/pyobjc_framework_libdispatch-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ce51a4e729c3d549b512721bef502f5a5bdb2cc61902a4046ec8e1807064e5bb", size = 15704, upload-time = "2025-10-21T08:10:50.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d0/71bc50c6d57e3a55216ebd618b67eeb9d568239809382c7dfd870e906c67/pyobjc_framework_libdispatch-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:cf3b4befc34a143969db6a0dfcfebaea484c8c3ec527cd73676880b06b5348fc", size = 15986, upload-time = "2025-10-21T08:10:52.515Z" }, +] + +[[package]] +name = "pyobjc-framework-libxpc" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/d3/e03390b44ff0c7c4542f5626e808f80f794e93a34a883377339cc1a18b0b/pyobjc_framework_libxpc-12.0.tar.gz", hash = "sha256:bf29f76f743a2af6cc5e294b34d671155257ef3f9751f92b821ecae75a9e7e52", size = 35557, upload-time = "2025-10-21T08:34:42.058Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cd/74/8fbdea024ce3863bd598c96c3d614e331125ba17814fd84c3a3957712469/pyobjc_framework_libxpc-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:97285c0c8c61230e13b78e0e4a12adcaca25123c2210ea6f36372c17c70ccc5d", size = 19627, upload-time = "2025-10-21T08:10:57.143Z" }, + { url = "https://files.pythonhosted.org/packages/e8/06/9c7274fe458b66a8fe562a370e3a6523904d88c6057dc2f2eccd978cd474/pyobjc_framework_libxpc-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba7eee4e91161c04055ffb94986afb558c6e5a43ecda175b345c7297c312f756", size = 19736, upload-time = "2025-10-21T08:10:59.653Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f8/6e800bf2b25da4ead85a4a5c8ee866f02a2f1747ee2b4fe5c7d11df0b624/pyobjc_framework_libxpc-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:edb8f63b3ab39b22bfa4db028c45bb953115b7cadbeadaef8f558e2e58ee2752", size = 19737, upload-time = "2025-10-21T08:11:01.887Z" }, + { url = "https://files.pythonhosted.org/packages/21/07/4001b087b3151c9674ab2c63c2d173e3ce0bed6dd91ca899665aee424a55/pyobjc_framework_libxpc-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:ca57eca41b50a4216a1eab550e6abcd865fc40f948b2df9822a589155f041501", size = 20316, upload-time = "2025-10-21T08:11:04.241Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/09ef28d6e55f59afbf964f7915b41a6e13fdff666578dc542fc87b1f9b58/pyobjc_framework_libxpc-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:05037c18d24816c70c8c8e3af6ad4655674914ac53cb00beadceadd269f1dd50", size = 19460, upload-time = "2025-10-21T08:11:06.802Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a1/8c7a1e8721179d5fba091ad2db650cc3d41050cf4a3bd4c46ebfad367274/pyobjc_framework_libxpc-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1ef89e6892305c412d7e0d892ca0faf404b7b19b403a599cdda88f27287f7ce0", size = 20030, upload-time = "2025-10-21T08:11:09.46Z" }, +] + +[[package]] +name = "pyobjc-framework-linkpresentation" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f8/35/63a070df5478caa26b5babe80002f4cca6fe2324061dd11a9b6c564c829b/pyobjc_framework_linkpresentation-12.0.tar.gz", hash = "sha256:e98d035cbe943720dbb28873b510916c168a27e80614cf34b65c619c372e8d98", size = 13373, upload-time = "2025-10-21T08:34:43.858Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/0a/43ef70f68840ebaff950052b23be84ef3f9620ca628a56501a287f8bfec7/pyobjc_framework_linkpresentation-12.0-py2.py3-none-any.whl", hash = "sha256:d895cada661657c3d43525372ab38294352cceba7a007ee8464af5ce822153c7", size = 3876, upload-time = "2025-10-21T08:11:10.904Z" }, +] + +[[package]] +name = "pyobjc-framework-localauthentication" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/20/6744b25940d9462e0410cadd6da2e25ea3c01e6067a1234d8092ae0a40fa/pyobjc_framework_localauthentication-12.0.tar.gz", hash = "sha256:6287b671d4e418419d8d5b2244616d72f346f6b8a8bc18d9a6bccb93a291091c", size = 30327, upload-time = "2025-10-21T08:34:46.643Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/44/d5df20bd83f83cf789278df5a3efc6054c72eddb42dd85c7d5ed3baf98dd/pyobjc_framework_localauthentication-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1bb42a6866972676b63afd53cc96be4e720a48929eebfa18fdd5c3ef763270a8", size = 10768, upload-time = "2025-10-21T08:11:15.316Z" }, + { url = "https://files.pythonhosted.org/packages/9c/01/f1af23b0c97ec7ecb9b88fe28104adc2fdd10c08f25a12935e75ceae70c1/pyobjc_framework_localauthentication-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:491e99d903930edbcffc27ee1f84902509bdd0b9d951464214603dc348f0e438", size = 10782, upload-time = "2025-10-21T08:11:18.694Z" }, + { url = "https://files.pythonhosted.org/packages/25/90/5304a84dc35d432c5189e7f1cc971a2da339ef32208364829808decc5679/pyobjc_framework_localauthentication-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:44c895e8bceea74532f01c5d45e57230c37f80c4dd3b5a4928deffe674a27a77", size = 10786, upload-time = "2025-10-21T08:11:20.462Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a8/b408b2a2eb0c7e2846dd6f6e5efad0db78d5628b7d82f5040d2ddf32b4bf/pyobjc_framework_localauthentication-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c6ab5aee535981e699c3248692eb02b52216dbe1ee7d5f0fe148be3672eaa5b8", size = 10938, upload-time = "2025-10-21T08:11:23.758Z" }, + { url = "https://files.pythonhosted.org/packages/1b/08/74582ce5f66598c45f9f64ad6389a00ef2408663450dd604e568a3bdbf14/pyobjc_framework_localauthentication-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:4357e9f741cdbe59edb5bc151b34d25ad3637074339e3c689322b72a367af800", size = 10851, upload-time = "2025-10-21T08:11:25.912Z" }, + { url = "https://files.pythonhosted.org/packages/ec/72/dcaa61b77513cea50843390dc4faf970d76bbd7f4b299349393151a928e9/pyobjc_framework_localauthentication-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:032dc56c09f863df593ed8c4dc0a4b605e0dd5db25715f4f6d61e88d594db794", size = 10989, upload-time = "2025-10-21T08:11:27.671Z" }, +] + +[[package]] +name = "pyobjc-framework-localauthenticationembeddedui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-localauthentication" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/b9/b0ebb005d1a96733463e811f60b0cc254bef3bb8792769e22621d1af80cb/pyobjc_framework_localauthenticationembeddedui-12.0.tar.gz", hash = "sha256:6f54afb2380a190c0a3fb54f26cd1492ccc0eb9ce040cd20c2702c305dd866da", size = 13643, upload-time = "2025-10-21T08:34:48.457Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/80/cfa1df39d32329350c9eec7b84a4cb966fe62679c463277bcfb75e8a03e0/pyobjc_framework_localauthenticationembeddedui-12.0-py2.py3-none-any.whl", hash = "sha256:0e78a1b41a47ca28310b4bece72bd52ba744a7f3386b8558d1b57129161a44bc", size = 3998, upload-time = "2025-10-21T08:11:29.039Z" }, +] + +[[package]] +name = "pyobjc-framework-mailkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/f0/f702efc9fe2a0c0dbb44728e7fd1edd75dd022edc54d51f2cb0fa001aaf0/pyobjc_framework_mailkit-12.0.tar.gz", hash = "sha256:98c45662428cfd4f672c170e2cc6c820bc1d625739a11603e3c267bebd18c6d8", size = 21015, upload-time = "2025-10-21T08:34:50.99Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/4a/d5a86176153459264339d4c440dbc827e6f262788218534ce15c50ce37ab/pyobjc_framework_mailkit-12.0-py2.py3-none-any.whl", hash = "sha256:ef1241515f486a91ef6d5c548043ceb0de54103e76232d6c14d3082c0e99fe2e", size = 4880, upload-time = "2025-10-21T08:11:30.909Z" }, +] + +[[package]] +name = "pyobjc-framework-mapkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-corelocation" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/6d/6392039d550044b60fe2f716991c2543674b62837eed61254f356380a6f2/pyobjc_framework_mapkit-12.0.tar.gz", hash = "sha256:15b6078243797aea2fbf0eee003c2868fae735ce278db0b25b9aade01cf9564a", size = 63945, upload-time = "2025-10-21T08:34:55.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/0f/69c419cb574e8c873adbc37ddc69da241a7e6f1bb53d88b03eeb399fbde5/pyobjc_framework_mapkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f764a0fa8fc082400a3ad3cf2e2ac5fddabab26e932c25cae914a9c3626e4208", size = 22500, upload-time = "2025-10-21T08:11:36.019Z" }, + { url = "https://files.pythonhosted.org/packages/63/10/135fdfc7dee64c03fc0acfeaa9f2d13c5053558a0bd532dec00f210049a2/pyobjc_framework_mapkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8e59fc3045205015a75fd6429324a16d4c7c00f5fa88b5d53c5d10d955768821", size = 22515, upload-time = "2025-10-21T08:11:38.579Z" }, + { url = "https://files.pythonhosted.org/packages/85/08/83220d516eb0a95956569c4e4318951a8533f34cc38c7368c56247f5c428/pyobjc_framework_mapkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2c08689b82102767c71a81643181180e512a1316a774b99fcd1f8acc7b12d911", size = 22545, upload-time = "2025-10-21T08:11:41.746Z" }, + { url = "https://files.pythonhosted.org/packages/ff/65/2d66304c0edb6b64d447f1ab35abcf5f3a59476aa08b5bf032aa5ba105fd/pyobjc_framework_mapkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1a4274ad4680887a39354f98b4c5cbabf4155d0a3277bd6b64417c3cd3a30748", size = 22722, upload-time = "2025-10-21T08:11:44.291Z" }, + { url = "https://files.pythonhosted.org/packages/46/8f/6106799ec49d5ee8fbc3e821b0f6729594d90242785ebbccf4334aa41890/pyobjc_framework_mapkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:18f0596305c796cb4421b6b130903ac731a844dde6cd4c4955350c950ad7a78e", size = 22570, upload-time = "2025-10-21T08:11:46.756Z" }, + { url = "https://files.pythonhosted.org/packages/f7/01/a683baad57f65e233b07568ca44fcfc2f5a584ddb4f16ee436671421d51f/pyobjc_framework_mapkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:e5c6a91a75dfcc529376db0931ee0a029a5ad355a8fbddc4b6010155a1e716ea", size = 22785, upload-time = "2025-10-21T08:11:49.515Z" }, +] + +[[package]] +name = "pyobjc-framework-mediaaccessibility" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7b/34/8d90408cf4e864e4800fe0fc481389c11e09f43dbe63305a73b98591fa80/pyobjc_framework_mediaaccessibility-12.0.tar.gz", hash = "sha256:bc9f2ca30dea75b43e5aa6d15dfbd2ec357d4afad42eb34f95d0056180e75182", size = 16374, upload-time = "2025-10-21T08:34:57.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/36/74b3970406cf5f831476f978513fc6614e8f40c1eb26f73e3a763e978547/pyobjc_framework_mediaaccessibility-12.0-py2.py3-none-any.whl", hash = "sha256:391244c646abe6489bd5886e4a5d11e7a3da5443f9a7a74bbd48520c19252082", size = 4809, upload-time = "2025-10-21T08:11:51.018Z" }, +] + +[[package]] +name = "pyobjc-framework-mediaextension" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9d/7b/8ecced95e3a4f5e8fc639202bbdebb1ffbe444341b63f42f732b718cad00/pyobjc_framework_mediaextension-12.0.tar.gz", hash = "sha256:af68dd3cc6a647990322e55f6b37b63da783ad400816c238a8bae6f2fea72a07", size = 39809, upload-time = "2025-10-21T08:35:01.292Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/44/01c205b2b9b98e040bef95aa0700259d18d611fc3f1e00be1a87318e8d99/pyobjc_framework_mediaextension-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30f122f45bf0dc2d0d48de1869d1364e87b1d3ab3c66de302cd9c9a08203b00d", size = 38973, upload-time = "2025-10-21T08:11:58.122Z" }, + { url = "https://files.pythonhosted.org/packages/8b/70/d3e62741d49559869fc4d606b325a5c3f60aeeef736409d559d0dc1e4ca4/pyobjc_framework_mediaextension-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6ebf5db7141c16e59bdc2f3482a182da7a3db4bfb72ca4e5fe11be3d09e03ba5", size = 38993, upload-time = "2025-10-21T08:12:01.348Z" }, + { url = "https://files.pythonhosted.org/packages/be/79/13074763bd2e6f74f7fc348fae0d98e719c0ae3d60138176350cc0ef96ac/pyobjc_framework_mediaextension-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6aece94b2f169ea5d40f1a3d2aef1303bb5e60007b998256b02be7c186cd2417", size = 39004, upload-time = "2025-10-21T08:12:04.987Z" }, + { url = "https://files.pythonhosted.org/packages/80/9b/cec1662e6c4b2cdc5ef1ad6efce6a4c29ee190a07deeaa91939ea811fe58/pyobjc_framework_mediaextension-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dac2b40b58d23d4beaf48e816e9f40acf3460fe0e3e07a0b370540e3aa2b5b1", size = 39207, upload-time = "2025-10-21T08:12:08.507Z" }, + { url = "https://files.pythonhosted.org/packages/2e/15/92bae64fd90f98bcaf9cf61b3e8d4ed38a5b1d10a68606edd237fdcbce51/pyobjc_framework_mediaextension-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fcc00c8f07c2c4317db230ac69dc5b7b1fc92e45ba7b1d7d22b733dd33055939", size = 38993, upload-time = "2025-10-21T08:12:11.971Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8a/fe2cae7146797c8b534f8c37699c5853c7492df59582074caef6120dcf6b/pyobjc_framework_mediaextension-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ba4c141613b908623b56af02ca6ebaea7d75679efbbe5dbf4865a3095e4544e4", size = 39199, upload-time = "2025-10-21T08:12:15.46Z" }, +] + +[[package]] +name = "pyobjc-framework-medialibrary" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/04/27/731cc25ea86cce6d19f3db99b1bb14d350ec6842120f834d7cc6f0001bab/pyobjc_framework_medialibrary-12.0.tar.gz", hash = "sha256:783b4a01ba731e3b7a1d0c76db66bc2be7ef0d6482ad153a65da7c996f1329cc", size = 16068, upload-time = "2025-10-21T08:35:03.639Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/57/5abdc5ef3ddd8a97bbcc0e9a375078f375d10f7e30222e1bef5348507fd2/pyobjc_framework_medialibrary-12.0-py2.py3-none-any.whl", hash = "sha256:f2a69aa959bf878bf6ce98d256e45d5ed19926f0d81d9ecbabd51ffdd2b54d18", size = 4372, upload-time = "2025-10-21T08:12:16.955Z" }, +] + +[[package]] +name = "pyobjc-framework-mediaplayer" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/98/58/022b4daa464db3448be0481abefcf08634b2bc3f121641eb33dfb9e1ee03/pyobjc_framework_mediaplayer-12.0.tar.gz", hash = "sha256:800c5a7b6652be54cbeefb7c9b2de02a7eaec9b7fef7a91c354dfc16880664e7", size = 35440, upload-time = "2025-10-21T08:35:07.076Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/2b/968ae22ef293c4b3f0373a28dd188156097b38494a7deadf30448b5666c7/pyobjc_framework_mediaplayer-12.0-py2.py3-none-any.whl", hash = "sha256:c754087dfdbd065bceb31cc224363e91b05305d530db4295cffbb0c3ae0613e4", size = 7131, upload-time = "2025-10-21T08:12:18.622Z" }, +] + +[[package]] +name = "pyobjc-framework-mediatoolbox" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/68/18/c7db54e9feafab8a201d05a668d4ffc5272ea65413c1032e1171f5bb98ca/pyobjc_framework_mediatoolbox-12.0.tar.gz", hash = "sha256:fcf0bd774860120203763e141a72f11aeeb2624c6ccd9beab4c79e24d31fb493", size = 22746, upload-time = "2025-10-21T08:35:09.437Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/6a/5a15a573fce30d1302db210759e4a3c89547c2078ff9dd9372a0339752ca/pyobjc_framework_mediatoolbox-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6f06e1c08b33eb5456fec6a7053053fddbe61e05abeac5d8465c295bd1fb19cd", size = 12667, upload-time = "2025-10-21T08:12:22.442Z" }, + { url = "https://files.pythonhosted.org/packages/db/f2/553237e5116fd31f384d2cac449c93d8dbf66f856f5c39de967c60a829e0/pyobjc_framework_mediatoolbox-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8241e20199d6901156eac95e8b57588967f048ef2249165952d6a43323a24d5f", size = 12826, upload-time = "2025-10-21T08:12:24.387Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ea/a2e521193d4d8dda383567959ba268335bb923f172cfc4adf4c0ea2dd045/pyobjc_framework_mediatoolbox-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bc2c679aca82e5a058241da80198765713e247f824890cdeac6660003c9339af", size = 12837, upload-time = "2025-10-21T08:12:26.308Z" }, + { url = "https://files.pythonhosted.org/packages/38/f2/039e5debaade9a90a2ae62a5bd8f74a3da4ab21be9dced0b4b41fb021b8e/pyobjc_framework_mediatoolbox-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:768267825941f0c61d124338aaa28cc5b37b321bc07a029959a03d4e745a13f6", size = 13432, upload-time = "2025-10-21T08:12:28.458Z" }, + { url = "https://files.pythonhosted.org/packages/88/14/4eb75241eb1bf63f088463ba90927016f21dcc8d3c717be83e4c3a47a621/pyobjc_framework_mediatoolbox-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:859455a780b88a9102f808b64aeecb67ba2c9d1951b77edf5228fe5edf2f26a9", size = 12823, upload-time = "2025-10-21T08:12:30.347Z" }, + { url = "https://files.pythonhosted.org/packages/10/81/850825ac65a6012fc13173113f898951fc8396f7d31a32c55f4712381fa8/pyobjc_framework_mediatoolbox-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:2b1d568651cf7106962057e5aeeb49484141cf5c89efdd0bb01480a2a13e307b", size = 13425, upload-time = "2025-10-21T08:12:33.192Z" }, +] + +[[package]] +name = "pyobjc-framework-metal" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/fe/529b6061e9d2012330fd5089fb9db3b56061557ca97762c961688eca41ad/pyobjc_framework_metal-12.0.tar.gz", hash = "sha256:1a4c08118089239986a3c4f7b19722e18986626933f0960be027c682a70d8758", size = 182133, upload-time = "2025-10-21T08:35:21.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/b3/e364e20ca7929eb805d7bebb462cbb5d864ae2e874cf6488fdecaea165e5/pyobjc_framework_metal-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eed803a7a47586db394af967e3ad0b44dc25940525a08aa12fa790e2d5c8b092", size = 75931, upload-time = "2025-10-21T08:12:45.459Z" }, + { url = "https://files.pythonhosted.org/packages/3f/90/3b5c7048f158a6c3aa2e0e04b3ec746e7862ac43c931e14337188e7550ae/pyobjc_framework_metal-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dae747eae25599d2e5a42f832b1e1e25afbecab78a4a193f8dccfc2add85afe3", size = 75852, upload-time = "2025-10-21T08:12:51.236Z" }, + { url = "https://files.pythonhosted.org/packages/29/e2/640e8ca7c55b73c44e462ac6f80a34ee1fae1c45b945020dbf59b7909144/pyobjc_framework_metal-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8f902490f46203f2f97e8bba7980b608fa653103b0e2a5e3ab2f6099abb4723a", size = 75881, upload-time = "2025-10-21T08:12:57.427Z" }, + { url = "https://files.pythonhosted.org/packages/2a/8b/275c9ad42814a31c7afe0d1c2147cfaf2ddf96354247167900141702f8c4/pyobjc_framework_metal-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e147b7138ca953bc32be546dc34d10932552f2eee6ac83e438df3d0cc6f25c50", size = 76428, upload-time = "2025-10-21T08:13:03.051Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/5c970d719f4ce23910d59bbe342ad621739ef81720cdd34976127fdd5869/pyobjc_framework_metal-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:877d4dc62d9086fe0e1007cd6a4c3d310fb8692311264a7908466f0f595f814b", size = 75876, upload-time = "2025-10-21T08:13:08.827Z" }, + { url = "https://files.pythonhosted.org/packages/e6/7c/8fd303cae8afc6c8d748194c6eb6cf8684bf465c796b4c949f92d72ea156/pyobjc_framework_metal-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:85dc55e77bf36e3217b81c27f0c17398959fce45acda917db2af7096d8ca90ec", size = 76499, upload-time = "2025-10-21T08:13:15.324Z" }, +] + +[[package]] +name = "pyobjc-framework-metalfx" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/22/dae4a062b18093668ea6e4abd7d0a4b122ee2e67f8482804a93baa7539f0/pyobjc_framework_metalfx-12.0.tar.gz", hash = "sha256:179d1f1f3efa42cbd788e40d424bf5f0335d72282c766d9f79868b262904579b", size = 29852, upload-time = "2025-10-21T08:35:24.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/fb/77f251307a6d92490a01a07815f1b25f32dd1bded15f1459035276088cc0/pyobjc_framework_metalfx-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:600e4b02b25d66e589bc5d3fbc91d55b0ac04cef582bac33a9f22435513dd49b", size = 15034, upload-time = "2025-10-21T08:13:19.456Z" }, + { url = "https://files.pythonhosted.org/packages/fe/73/52660e5aa3ce662ffa8bd64441023dd38650519346a648376e96ac0a80e7/pyobjc_framework_metalfx-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:41b60f5309a6d3202a2d452ead86cfb3716e9f56382fd410b8f21402a752a427", size = 15064, upload-time = "2025-10-21T08:13:21.368Z" }, + { url = "https://files.pythonhosted.org/packages/f1/79/2b9b4fba3820c6df6b9e2dd5802900edf5dcac1688fd5ef5490cfe1c7033/pyobjc_framework_metalfx-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:faf296274f00912bdcba4bf1986e608fcbf6c8f2ef3bd6b0a9e5f7bd35c4a8d8", size = 15079, upload-time = "2025-10-21T08:13:23.409Z" }, + { url = "https://files.pythonhosted.org/packages/4c/69/d9a19e982b7d6a5d28cede0a9c251a2944aa09fcf24e42efb1a6228f7eb7/pyobjc_framework_metalfx-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1c61569bc4b95c4f6ca9bd878f3a231b216d13abbd999f55d77da2a20d8232de", size = 15298, upload-time = "2025-10-21T08:13:25.73Z" }, + { url = "https://files.pythonhosted.org/packages/f6/bb/b636890598aa9dd2ca7a439e1ca9b62c2badfb9f0a2a3c675450c1348b59/pyobjc_framework_metalfx-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:241a2857509714dfe0e8e15dbcdd226d9540266151186f889fdca360d619477f", size = 16353, upload-time = "2025-10-21T08:13:28.478Z" }, + { url = "https://files.pythonhosted.org/packages/f3/36/337d6fbf8b92ae38d1f38110462269e87841fb7b3f4f967e694020a639b7/pyobjc_framework_metalfx-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:951bc0176a5761100cbaa880977868797df4282ef7428f35210764e6cf7fc192", size = 16600, upload-time = "2025-10-21T08:13:30.547Z" }, +] + +[[package]] +name = "pyobjc-framework-metalkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-metal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/75/e9/668136ba83197b2ff34c018710d55abebd8de0267a138f12df0dde17772d/pyobjc_framework_metalkit-12.0.tar.gz", hash = "sha256:e5c2c27fc5ecd7dd553524cb3ccce7cbd0fa62d39e58e532a06ce977069a7132", size = 25878, upload-time = "2025-10-21T08:35:27.65Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/30/f9c05e635d58c87f8aaa7c87eeb6827b6caaf5809ef9e8da3ebd51de60a7/pyobjc_framework_metalkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:35d7cf3f487d49f961058d54e84f07aead6d73137b7dd922e13ea8868b65415d", size = 8746, upload-time = "2025-10-21T08:13:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/72/3c/e16552347b21d27fc29cf455d28fb3f0e5710b63e1dffdb56f3495d382bf/pyobjc_framework_metalkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ad4e3184f18855bfe62ca9a7f41d4de8373098eaef03c2dbd041d5ffe0d38fa2", size = 8763, upload-time = "2025-10-21T08:13:36.303Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3b/a3cd18064ce891bb3d5bdf06d2674da0d7af02e20728cbe6532ca7b8b383/pyobjc_framework_metalkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2794b834d70821777e44e876740aa0254669749711d938759c0a63cf0056ea3b", size = 8780, upload-time = "2025-10-21T08:13:39.352Z" }, + { url = "https://files.pythonhosted.org/packages/d9/26/31bc69b7e926415c8289b997a04fee7bc397919edddc22a97d0a31262c05/pyobjc_framework_metalkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f64a827edf6777ea0b4f93f035bac23042b7de6101e80306d37d0ea175aeb79a", size = 8931, upload-time = "2025-10-21T08:13:40.942Z" }, + { url = "https://files.pythonhosted.org/packages/70/4b/4f9e1c46a5a790a2dc497b9c466e1b352a2e491c331f88db8a7638af9406/pyobjc_framework_metalkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d494e8e16d24174c957b67e35bee18fcaf8b43caf3d9f51d27c6454a9fb9529e", size = 8830, upload-time = "2025-10-21T08:13:42.608Z" }, + { url = "https://files.pythonhosted.org/packages/5c/19/eac92586b4e87551c2d33c87356af0a03c5ddae6cd17f85d5f0b765e93cf/pyobjc_framework_metalkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8192274350db236a06504486bedbe06f9c857b619cd83e176e6d20c328320dac", size = 8981, upload-time = "2025-10-21T08:13:44.23Z" }, +] + +[[package]] +name = "pyobjc-framework-metalperformanceshaders" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/5f/86c48d83cf90da2f626a3134a51c0531a739ad325d64f7cf3e92ddcab8bf/pyobjc_framework_metalperformanceshaders-12.0.tar.gz", hash = "sha256:a87af3d89122fd35de03157d787c207eebd17446e4532868b8d70f1723cc476f", size = 137694, upload-time = "2025-10-21T08:35:37.068Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/6f/e5d994c0a162eb7e1fadb1e58faa02fffa61b6f68fdf50d3e414a80534bb/pyobjc_framework_metalperformanceshaders-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:90fbdceba581a047ffa97a20f873d2b298f4ee35052539628ece2397ccd4684b", size = 32991, upload-time = "2025-10-21T08:13:50.596Z" }, + { url = "https://files.pythonhosted.org/packages/55/fe/d6f20bec6b508c5b5fe5980c82a36e12c21bdc3f066d51a17ed39b5c8fbd/pyobjc_framework_metalperformanceshaders-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b7ab6a6ce766f0cc3d848f74cfb055d7d07084155298d7f0e4537cfb4a80f58c", size = 33246, upload-time = "2025-10-21T08:13:53.668Z" }, + { url = "https://files.pythonhosted.org/packages/fa/d7/b82d26abfb909d850c91f23b8172ffe4e0931aeadf3a56d210767e79f887/pyobjc_framework_metalperformanceshaders-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b2dc5df8f8c27c468aa9795fa8960edb9e42ad3d5d5727a4ac04d9bf391f3d6c", size = 33268, upload-time = "2025-10-21T08:13:56.659Z" }, + { url = "https://files.pythonhosted.org/packages/77/69/caaa23c8b20963180f188e9dd30f7663fee0a1ecef3abc0456506da5e725/pyobjc_framework_metalperformanceshaders-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2275c3b28d9c0f5cd53ec03145f3acda640ddda9c598582f4160e588c70f0cd1", size = 33464, upload-time = "2025-10-21T08:13:59.661Z" }, + { url = "https://files.pythonhosted.org/packages/ae/14/5ac7db29658d21233fda1824d5b5f75ece202567d7125e66fdc6a7eeb345/pyobjc_framework_metalperformanceshaders-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:48c56fec469f70364171890b371808aa8aba24289aecae6aecf4c34a73b326eb", size = 33332, upload-time = "2025-10-21T08:14:03.109Z" }, + { url = "https://files.pythonhosted.org/packages/3c/36/b357c4cacb231bd1691c7ea124dc984304b5b3cbf4258374f154e24a8b0c/pyobjc_framework_metalperformanceshaders-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:331e1d06a486be9a41f2c0b80386bbdf59a6f3518873016589daef5416439090", size = 33546, upload-time = "2025-10-21T08:14:06.203Z" }, +] + +[[package]] +name = "pyobjc-framework-metalperformanceshadersgraph" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metalperformanceshaders" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/e9/4a57eb83ecb167528e3ae3114ad1bf114c56216449da5c236ae41f8ad797/pyobjc_framework_metalperformanceshadersgraph-12.0.tar.gz", hash = "sha256:8323f119faa1d2a141e9ac895b7b796e016e891e70ef0af000863714af845a21", size = 43030, upload-time = "2025-10-21T08:35:41.292Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/21/b4e0f21f013c54e0675b57a5523ee1c13b1bea73b34455a2450a92e9cc0e/pyobjc_framework_metalperformanceshadersgraph-12.0-py2.py3-none-any.whl", hash = "sha256:3e8f978d733e911fff61b212a27553142596edd53b80a630b20a0db06f59a601", size = 6491, upload-time = "2025-10-21T08:14:07.994Z" }, +] + +[[package]] +name = "pyobjc-framework-metrickit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/30/89f4731851814be85d100fd329fa1aa808648c73d702c9835b2ad9d0628f/pyobjc_framework_metrickit-12.0.tar.gz", hash = "sha256:ddfc464625433ab842a0ff86ea8663226f0dee8c75af4ac8f7e7478fef4fdddd", size = 28046, upload-time = "2025-10-21T08:35:44.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/d1/a69b591cc5ab64ae84f0d34a7ed9b49f7e078ab8fb73c834bc34d81f2b38/pyobjc_framework_metrickit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b53cb8350fea3bc98702d984f1563c4e384773303153a76ecf2109cc89a5a9b", size = 8112, upload-time = "2025-10-21T08:14:12.54Z" }, + { url = "https://files.pythonhosted.org/packages/8c/9e/e6e14983b629c418a2230d31ca1fd3870556e1b303a18aade1dd669f7927/pyobjc_framework_metrickit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8e48f4fd67300a276873676ed3defba58cec6eab235956cb8dcdf5e2f56b9614", size = 8131, upload-time = "2025-10-21T08:14:14.008Z" }, + { url = "https://files.pythonhosted.org/packages/11/88/c176fcd66f8e3028605a0953b5d5c9200557e494f17a0728e9ab5f721cf3/pyobjc_framework_metrickit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bee6b2bf4add4171a7aa5444bcd7015202af9d993bc8b4efbbdedc35f5cd42c", size = 8144, upload-time = "2025-10-21T08:14:15.939Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/7d670440f8330d76b2b9a942598adf51d0b04347919c603fbf9f4f66c345/pyobjc_framework_metrickit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c03927f02b27c929d8883e829785c721a1031e9bd8a674a71f6dacc3ab8ffc4", size = 8282, upload-time = "2025-10-21T08:14:17.861Z" }, + { url = "https://files.pythonhosted.org/packages/ba/4b/29976e2cd5396fae84abbd5d6b0bfa7159bdede5a6c7762b90583187cf17/pyobjc_framework_metrickit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3c0b3a7892991f4de6e828fc4075409a1962eafbd773a61e689ef120159d41fb", size = 8196, upload-time = "2025-10-21T08:14:19.418Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/d261d5b36d6bc3f9fb25fe932633cf01a29cc870b94e37d4fc7d4da1a59d/pyobjc_framework_metrickit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:56cefe73b47a42e79acf8cbd1e453dba345afa7908b2d3efc355d394a7d74150", size = 8343, upload-time = "2025-10-21T08:14:21.51Z" }, +] + +[[package]] +name = "pyobjc-framework-mlcompute" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/054839433183983c923d91e383cff027a8d6dc2f106d485869584fa4c030/pyobjc_framework_mlcompute-12.0.tar.gz", hash = "sha256:64bdaf38c564c583dbb242677acd8b4e0d2e100ea651953f61fecbb5ba94a844", size = 40717, upload-time = "2025-10-21T08:35:48.066Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/5d/aa7eaa1a5a3d709f8df2955b2898048e666d54e25473e74854384ecf4c06/pyobjc_framework_mlcompute-12.0-py2.py3-none-any.whl", hash = "sha256:ba172ffd3b3544a3dccd305b91b538da10f80214c3d8ddd2a730a5caa75669c7", size = 6753, upload-time = "2025-10-21T08:14:23.019Z" }, +] + +[[package]] +name = "pyobjc-framework-modelio" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/a1/e4497a07fdbe81ef48fd33af1123ba2613d72a59f9affa6aeb0b302dc85f/pyobjc_framework_modelio-12.0.tar.gz", hash = "sha256:15341997259521e132b2010c0bea5928143e47de6772a447d4d1c834db0f7f01", size = 66906, upload-time = "2025-10-21T08:35:53.139Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/30/6b6c417fc491dea3370e8a74a3d9863f83dba59d1ae742b641fafeecb240/pyobjc_framework_modelio-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0792e2330a8362e5ebc1d42766abed2a22d735179a604432e0bb0d1ad7367dbe", size = 20187, upload-time = "2025-10-21T08:14:28.188Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/385ca68bcac6bda97facce67db86ee9a2fd1f723be2da492a2643f86aaf7/pyobjc_framework_modelio-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2943f5378a0f3494816e2ffad11ec02dfaf8a446b50863f1daaf5eb232a4cffb", size = 20203, upload-time = "2025-10-21T08:14:30.998Z" }, + { url = "https://files.pythonhosted.org/packages/37/5b/8141ca4b2b014343c92b916eca8640b43b5f3a14aa6bbba6048907bc62d9/pyobjc_framework_modelio-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0df12e3251b180fa40a5f6328f5719839e6a1815a64d7cd10ab349d7777135cf", size = 20221, upload-time = "2025-10-21T08:14:33.286Z" }, + { url = "https://files.pythonhosted.org/packages/9d/1c/3e9e303d88a0ad878fd6c23107836185da9f4b81b2777e327b5838fd2880/pyobjc_framework_modelio-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:de869883bc1c6d376ba5484fca7971a6c184c4e46e573d31a26f333ff1e86305", size = 20452, upload-time = "2025-10-21T08:14:35.625Z" }, + { url = "https://files.pythonhosted.org/packages/ed/75/e71deca023d4159c76da3faae3dff49bc5fa87eae14dfada07a884e5498c/pyobjc_framework_modelio-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d89883d1b5ba79fbc49c6513eea88c7cc57a4cd23446bb24301b52d19288c45d", size = 20189, upload-time = "2025-10-21T08:14:38.381Z" }, + { url = "https://files.pythonhosted.org/packages/73/df/ba2b49fb757075f67ba29ea6fdb519863753e140665edf4817a6e8c89f05/pyobjc_framework_modelio-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:38ee4a61cdaaed709c18a52dff285a678b179705b8105d3cc329d240fa085a00", size = 20436, upload-time = "2025-10-21T08:14:40.887Z" }, +] + +[[package]] +name = "pyobjc-framework-multipeerconnectivity" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/af/e1379399637fc292eae354e15a1a55037c9c198494f30f65c8a6cb3ad771/pyobjc_framework_multipeerconnectivity-12.0.tar.gz", hash = "sha256:91796d7a2b88ea2cc44c03474e6730e9f647a018406c324943c224c1f3ea1fc5", size = 23213, upload-time = "2025-10-21T08:35:55.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/84/4476ac81f33e897535fcb5975cfaf55c6e1bf7aa98a0d23f0882ab519869/pyobjc_framework_multipeerconnectivity-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dd2799edc92018080bf19acfe6e6d857365ce945003f7ff9afde55a28925ace5", size = 11993, upload-time = "2025-10-21T08:14:44.959Z" }, + { url = "https://files.pythonhosted.org/packages/35/82/48ed4a1bddf346893d6c048ac3b9f8cb4fe766b9cb9d1cc53c75b72bc513/pyobjc_framework_multipeerconnectivity-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:98b1007f5437c69cc551333ca17cf6b210d515bd90ef36ccb1cc93a0d300b0d5", size = 12014, upload-time = "2025-10-21T08:14:47.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/ac/5ab35302e2c4ce1d65fef94b5b5238b175d355f4fdf13d9ce712d9cb1f54/pyobjc_framework_multipeerconnectivity-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:73b36e3d1b5c813586de1c2f05f93c86f625d60754258c0599cede7edd8b282f", size = 12028, upload-time = "2025-10-21T08:14:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9c/0ce837d6be1f1d641f4d4e83c6646a44872d8e4a3083bdd233df95fb259b/pyobjc_framework_multipeerconnectivity-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1ed9397cb0923d91307284b14f8a66779a3e9699f1d2e5a6c3b0abc3fefc322c", size = 12211, upload-time = "2025-10-21T08:14:50.683Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/ef8ae7925925c20eb191bb929082f12ceedbc7c7e1b07417556b09cbebd8/pyobjc_framework_multipeerconnectivity-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d345e777362c190bd80e61f2ad646dcea08956db6460d55542bfa363deadfeef", size = 12001, upload-time = "2025-10-21T08:14:52.764Z" }, + { url = "https://files.pythonhosted.org/packages/e1/83/4751f168073fca6e94282495c18cbda0ac3f705998bebe7f49c81ee287df/pyobjc_framework_multipeerconnectivity-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:7e4dca99ee378430a4be66b319c841e3e3bcdc0d0a35e82f611c294380dbc663", size = 12224, upload-time = "2025-10-21T08:14:54.515Z" }, +] + +[[package]] +name = "pyobjc-framework-naturallanguage" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/91/785780967e0cf8f78ac2d69f3b7624d9fd52ec746bd655fb738fec584b39/pyobjc_framework_naturallanguage-12.0.tar.gz", hash = "sha256:a5fc834d9fe81cc2e45dd3749de3df0edfc9ab41b1c31efa4fcf0d00a51c9dfb", size = 23561, upload-time = "2025-10-21T08:35:58.811Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/0c/bfe280f01e61a2ef43f6fc341a8f039ff1e7a20283f159fda05c24f5c1b2/pyobjc_framework_naturallanguage-12.0-py2.py3-none-any.whl", hash = "sha256:acfb624e438a14285aaaa2233b064d875fe3895a0fc0578f67dc15fdba85e33b", size = 5330, upload-time = "2025-10-21T08:14:55.911Z" }, +] + +[[package]] +name = "pyobjc-framework-netfs" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/fd/f7df2b99f900856b15ea9cd425577cff4b7e0399c01b48fc317036e8067c/pyobjc_framework_netfs-12.0.tar.gz", hash = "sha256:0bbd02e171ba634c44a357763d3204f743af60004fd0a2bd76fd2e6918602c52", size = 14859, upload-time = "2025-10-21T08:36:00.739Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/bc/d17ecc6a17327d7a950af52b8a68c471d7b5689108d77b9c079ec2ccc884/pyobjc_framework_netfs-12.0-py2.py3-none-any.whl", hash = "sha256:a1251a56a4a0716ebb97569993c5406b3adaecd16c9042347e8bce14fa3a140f", size = 4169, upload-time = "2025-10-21T08:14:57.474Z" }, +] + +[[package]] +name = "pyobjc-framework-network" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/e0/a51caeb37e7e737392c53a45a21418fd14057b8abea7a427347fbd6a3d6b/pyobjc_framework_network-12.0.tar.gz", hash = "sha256:5524e449c22e3feda1938bf071e64cec149cea4f1459959f2e7de513a6c902ec", size = 57385, upload-time = "2025-10-21T08:36:05.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/c6/d83d5c4d7f4f63a6240ddec3dd52d6efe52f1b1edcd599f696845a3b6b66/pyobjc_framework_network-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:220be97a68eec81d4b2e9068c8936bf5ef7033916be034a0b93e5b932cf77a00", size = 19604, upload-time = "2025-10-21T08:15:02.103Z" }, + { url = "https://files.pythonhosted.org/packages/a5/cc/3cecf0d2a4ba79f0f6f44a119a0c41e790a96b6310819664e819b1e900b5/pyobjc_framework_network-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:22ee38233ff09bd9a76e067dce5f979bdc65c56959ed82c913e93259803828d9", size = 19623, upload-time = "2025-10-21T08:15:04.301Z" }, + { url = "https://files.pythonhosted.org/packages/00/88/d15c0414495d3cdb5305d560acd1dd510c5a8f301d3a0d2e7aa5e4416c4f/pyobjc_framework_network-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:168e331063b50c020b350c9426ff61d90f6400c5d953bb4e0ff6e23c76c5a96d", size = 19634, upload-time = "2025-10-21T08:15:06.856Z" }, + { url = "https://files.pythonhosted.org/packages/06/b2/d4ccf7e04e213d2a11c0de573e16ed461933901c12f0d7fc8cb9eac607ad/pyobjc_framework_network-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:d8783a4c83e7e4bc6c5e829e216e1e0f107bdbe51500a333dd2afe456bc2fabb", size = 19706, upload-time = "2025-10-21T08:15:09.822Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/588cba7bca8877c27d0903ef686043bb974ada9cd53625495342b2f17759/pyobjc_framework_network-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0c19e64b1bc2164671fe6cabe2885154201995a282ee02b1f3bd2caba792f23f", size = 19369, upload-time = "2025-10-21T08:15:12.61Z" }, + { url = "https://files.pythonhosted.org/packages/d5/9a/8fbfa8b7a930c83838110e194ed8c7bf4d7a94b4a78d7773d22d9a1114bf/pyobjc_framework_network-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1a9e47e4f2693ea773dcbe97f8c16ed5531b579a6b471656a5b003291a90a87", size = 19421, upload-time = "2025-10-21T08:15:14.893Z" }, +] + +[[package]] +name = "pyobjc-framework-networkextension" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/ab/27769fdb0af13c8ba781b052fa7e1b5c77944665bab3a85a39fbf9f08f50/pyobjc_framework_networkextension-12.0.tar.gz", hash = "sha256:fff9e747d2d5da8352649028abaabc610bc3fa2779573e70df216aff7c00cb44", size = 63197, upload-time = "2025-10-21T08:36:10.071Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/53/6d/b939daf7fdbceaa6a41d5ed594270675937744feb191140c423f6ee6c366/pyobjc_framework_networkextension-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:23205ca928a5af2dd7e0f7d723c0b7dde0eaec6b5a15d298bc22d4ff8e5ae8b6", size = 14372, upload-time = "2025-10-21T08:15:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/94/24/c460edf133f5b5d460cd5ae46c8e849a584a55cccacfe261a9b50b7303a4/pyobjc_framework_networkextension-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4b5e9054b750bdd77725629cb886c76b1b40b600914da3e6e1a4f8edba98718", size = 14386, upload-time = "2025-10-21T08:15:21.321Z" }, + { url = "https://files.pythonhosted.org/packages/36/b8/bdb501e1e0f32a1e4f20ceef81ef04c6e6584f928968a00dc1e3f17d27c3/pyobjc_framework_networkextension-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4738b8521873d1403fcbaa6c0282697a1104e53e229547232da2773bf37f096e", size = 14403, upload-time = "2025-10-21T08:15:23.681Z" }, + { url = "https://files.pythonhosted.org/packages/e6/c9/0643087a70694ddc3c80c5cd44fd379b00dffe17532351eaf2f18ea24daa/pyobjc_framework_networkextension-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a5e7b8d5b1811480e6f00bc6b4a89c2d2c3c8298ef906689541f01214e866b3c", size = 14546, upload-time = "2025-10-21T08:15:25.773Z" }, + { url = "https://files.pythonhosted.org/packages/cf/1e/1d2ebe00ffe2f4bd197534a1f8da80826b53bfd6312fe6bb6e76a3e46996/pyobjc_framework_networkextension-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:22efe55a39a8a36b5a3d68e3d527351a060b66fdf1c6c4e9c88bbe501e93684a", size = 14465, upload-time = "2025-10-21T08:15:28.134Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b7/47c4297f0d0cd08fb72c00f2d60d248ffe71801192d8f1c0c4a9ed23d5a6/pyobjc_framework_networkextension-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:930572f289ef6450521411834d55df207885cb2c81385d2256ca334a1f103869", size = 14599, upload-time = "2025-10-21T08:15:30.211Z" }, +] + +[[package]] +name = "pyobjc-framework-notificationcenter" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bd/76355e7ecdb558291c0699d825d962a1f53089645eee8e92dcc418aa13c8/pyobjc_framework_notificationcenter-12.0.tar.gz", hash = "sha256:ecec30ef99c440f7013eab2c147f413d9b87047eb3b4a6656ec58513f67fe61e", size = 21729, upload-time = "2025-10-21T08:36:12.827Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/1d/756379b05a43ceeead1a20fbd355c420436dc6f90a61dcedcbffe31eff7d/pyobjc_framework_notificationcenter-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e13c69f1e1042a79d5d883df0b6e79fdd19c5bc149b2ffdcca36ef4a80a5fd5c", size = 9882, upload-time = "2025-10-21T08:15:33.566Z" }, + { url = "https://files.pythonhosted.org/packages/d1/30/845b1a3e3d650f80e661eb7f960f80aaae7a8ce4d2578440f3f189c2cd9d/pyobjc_framework_notificationcenter-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156af0528623a79312cda912621bf05e4aecec27028cfd588f1a69240b38996a", size = 9908, upload-time = "2025-10-21T08:15:35.153Z" }, + { url = "https://files.pythonhosted.org/packages/a3/69/fff76d3fac81ed3a74aee9c302897114d1273de17132155919e3031bdb80/pyobjc_framework_notificationcenter-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3aa8371456c57a7de65b6073ace39106310284394749ed72c0b0e47dd92169bc", size = 9928, upload-time = "2025-10-21T08:15:36.797Z" }, + { url = "https://files.pythonhosted.org/packages/25/0c/62d484e4ca483446f777b5f1d2c43b62bc2da9c2e71fe6cc00ff24e1611e/pyobjc_framework_notificationcenter-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:367cda711515e60bf6259bf4e9f447c606a0f2a1a471b6a6d70a801ded653d2e", size = 10123, upload-time = "2025-10-21T08:15:38.747Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c7/2d71cf162b284f093d9784ecd08de38dbf8737f5a73c3760c92660afdfd5/pyobjc_framework_notificationcenter-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d8594430a18312c4c818696cf4c67d1054f2ced0304a2d17f16585b36a4fb76b", size = 9991, upload-time = "2025-10-21T08:15:40.411Z" }, + { url = "https://files.pythonhosted.org/packages/60/7e/8058987767d48f134939b467af39a46398e308153a01ea8b6fd339b2f779/pyobjc_framework_notificationcenter-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fac468fb2a86c8fb886bf99b73046ea522503bc6123ea3636a42ec88d54f84f9", size = 10198, upload-time = "2025-10-21T08:15:42.367Z" }, +] + +[[package]] +name = "pyobjc-framework-opendirectory" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/3b/da8e6c62df0b721683940737a12f324342ee25e321fe8d26457bc394523e/pyobjc_framework_opendirectory-12.0.tar.gz", hash = "sha256:1fdcd865486b984dd19aa6e1f6ac200d43d1fb12ca34b56b44978ad19ed0b2b7", size = 61060, upload-time = "2025-10-21T08:36:17.564Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/44/e761c1bcf2516561d144668f85a0adcc60e2866475e6af56293b9a57c4ea/pyobjc_framework_opendirectory-12.0-py2.py3-none-any.whl", hash = "sha256:009de69034f254381786ee14cabacbc892d05204127caaeae8fe05d57172fffa", size = 11855, upload-time = "2025-10-21T08:15:44.141Z" }, +] + +[[package]] +name = "pyobjc-framework-osakit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/f8/f861aaf97c03525d530e269f63132a5dad37db2766eb2c08c5db74e0121e/pyobjc_framework_osakit-12.0.tar.gz", hash = "sha256:1662e40c5e28a254ff611310ef226194c6e22f2b731d2e877930e22a715f2144", size = 17119, upload-time = "2025-10-21T08:36:19.863Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/8a/2fabeb3f0e7be46ee64c31f7d17200fb8198139c82bca57db5344e11d1b9/pyobjc_framework_osakit-12.0-py2.py3-none-any.whl", hash = "sha256:807400db5845daaee55dbb6fbc63eadbfc120d12f4e62cb6135cf29929821f54", size = 4171, upload-time = "2025-10-21T08:15:45.638Z" }, +] + +[[package]] +name = "pyobjc-framework-oslog" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ad/81/45878bbf7814e5cb6723f1cfd21e5a9f61ef2db5ce71cc32c66db89f31d2/pyobjc_framework_oslog-12.0.tar.gz", hash = "sha256:635548ab6cfd0201f6785d7c572bc7515eb0c2fe569e1b37f8742c164ea4b2cb", size = 21589, upload-time = "2025-10-21T08:36:22.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/83/d1d60ef0006bcf7f187074da7a6fc9e57aa7b8a470a440a537c52696b637/pyobjc_framework_oslog-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e2571519ccf58405896b9e5d1d64cfa7163f4da69a52460435eab67f185ad06", size = 7805, upload-time = "2025-10-21T08:15:49.407Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d3/423d57d64471a6974eb158979878a374d3cbddb6bce905ed31e979067eb4/pyobjc_framework_oslog-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73f57b66efa716a664d99c1fbe93e9bc6b854fad5f8dc3d0ce86da443aab5fdf", size = 7825, upload-time = "2025-10-21T08:15:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/99/56/411424aed9a4ef9a50c89a4e0e8dcc29fa7f35ccfc3215bead7e1dc596ce/pyobjc_framework_oslog-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f33975c15f4d0c9a3eeb644034525220b8f53d633bbf5258ea4efb36139e0d89", size = 7840, upload-time = "2025-10-21T08:15:53.003Z" }, + { url = "https://files.pythonhosted.org/packages/bc/27/c18fc593460113fed8e0c5c0d5ebd898621265281dcf750dedca9c8efbb9/pyobjc_framework_oslog-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:fd279fbc4aebfd57fd301d68b269dd00b46649ac25de054a4ca8f4276e02a2ac", size = 8020, upload-time = "2025-10-21T08:15:54.528Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ad/c19b4c3b69c19ba7355e1d64eae0d9e670c17b9b323e977e6b2621ae3e45/pyobjc_framework_oslog-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0be22d5da3f8d45f09959b25872bac1dcccc3ed91cd2402785141f6fc40ce149", size = 7887, upload-time = "2025-10-21T08:15:56.246Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ca/9edd613d6db985e8a618418a4cc9b3769ab0533eded138f25416c8060fb9/pyobjc_framework_oslog-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:750c82d2374959dcf4abbf682a9bb1bce2cfe24333a5c38e6fc5239cabbdaea7", size = 8084, upload-time = "2025-10-21T08:15:57.875Z" }, +] + +[[package]] +name = "pyobjc-framework-passkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/ca/4cdac3a3461f46261e70cbfb551eb51d6b0eac51eb918c6e685bc5c39566/pyobjc_framework_passkit-12.0.tar.gz", hash = "sha256:6a206195385a62472b71384799f85fb5c6316e819d9bdedf905efa150ec82313", size = 54214, upload-time = "2025-10-21T08:36:26.396Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/b4/db0a86a3cb1ea7ec03510d88030c6281314df7ce892c9e67118c921721a5/pyobjc_framework_passkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1e746b10867418fd0b6b8805f2e586ac17a66c94b6f3d7d637f27abbb9653ec7", size = 14091, upload-time = "2025-10-21T08:16:02.226Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b6/05fdd024b20a4785fc03e12011ea4258296e1edbb3a1cc3a0432edc0befa/pyobjc_framework_passkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fad8ecec6c16d4372fe18347106f1f451383fd19d7a80877e369d96e70e1703", size = 14110, upload-time = "2025-10-21T08:16:04.195Z" }, + { url = "https://files.pythonhosted.org/packages/5e/95/6401621bf1c7d4ef39b529219ac03be8a85d9c52d7398ea430cc64d00720/pyobjc_framework_passkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7b6a42e5a5096570b7423f7b1b4b2a1f96ac3fd8187e39d702350b6ba5e0c960", size = 14126, upload-time = "2025-10-21T08:16:06.163Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b3/8155a5599f9eb7dd5532185298458b08cb552be5730316b4583859780d70/pyobjc_framework_passkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5c880d60b7d43d5180f1643b553b848ebff87188a01a2d6f4ccf509d4da28255", size = 14283, upload-time = "2025-10-21T08:16:08.175Z" }, + { url = "https://files.pythonhosted.org/packages/63/cb/40ff8554c2d279a1da76f1980f9cac4b192525079b6eb9f0b58bb92b81c0/pyobjc_framework_passkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1f57f21badb615385ff0916cc40d6741684df430dd56b9472e4bb889fb10c285", size = 14135, upload-time = "2025-10-21T08:16:10.196Z" }, + { url = "https://files.pythonhosted.org/packages/27/8e/359e25846b4d1809412941e295a92e0b445fc7c5532bce9d61c3b359d97b/pyobjc_framework_passkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:3c599699efc44e674b0ab50dc35679ff03550e06b56aace9ff52ed3d374ab09a", size = 14288, upload-time = "2025-10-21T08:16:12.499Z" }, +] + +[[package]] +name = "pyobjc-framework-pencilkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/1d/c9ea9612680049a8b411acf817c77b18bae5180d8ad87753c172c9502b37/pyobjc_framework_pencilkit-12.0.tar.gz", hash = "sha256:efbead8c776bf9a24964586a70d937d54b087882b9b11a6e85478631e2a56f78", size = 17700, upload-time = "2025-10-21T08:36:28.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/d4/03f54c700d0278f6696cd9b3e5f65ab99aba3e5d026367b980d8ae566489/pyobjc_framework_pencilkit-12.0-py2.py3-none-any.whl", hash = "sha256:94794222210081205aa49f16f6c19be50c6ca73b598cbd8d8a1849bb1bf88075", size = 4218, upload-time = "2025-10-21T08:16:13.969Z" }, +] + +[[package]] +name = "pyobjc-framework-phase" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/a2/7de65c8a8c9eaead9f3435ef433c4cc36b6480fcaeb92799a331ffa9bcd9/pyobjc_framework_phase-12.0.tar.gz", hash = "sha256:f1c004cc26a136a6dd6a36097865f37d725bd4ba03c59c7d23859af2ce855ac7", size = 32756, upload-time = "2025-10-21T08:36:31.821Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c4/a6/5845a8710f2087199b512e47129f07f6c6a80d6eb3aa195f2c6a50bfe23a/pyobjc_framework_phase-12.0-py2.py3-none-any.whl", hash = "sha256:a520e94ac9163bd4c586bfefdb8a129a15c5fbda59d728c4135835e3ce5c6031", size = 6913, upload-time = "2025-10-21T08:16:15.556Z" }, +] + +[[package]] +name = "pyobjc-framework-photos" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/03/b6/db478ff16bf203a956a704de266c2f09e1a97cdbf386679724009d02dfce/pyobjc_framework_photos-12.0.tar.gz", hash = "sha256:3d910e0665e3b9ff9a72e43b82f2547cb33d4631e3b355e5d4cc3bae8089794b", size = 46460, upload-time = "2025-10-21T08:36:35.646Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/67/52/4cf272abba9dea78eaf3db8f03436520812c8486d7e65fecc093203f45f2/pyobjc_framework_photos-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:840fa12246293bfe2ef2412b2646bb988b91dbdb4b3748b457fd44f4b2a1e280", size = 12238, upload-time = "2025-10-21T08:16:19.291Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/693be9e255b04dc413b52b0c496df0297c67ee8bb6a89f02e780c4f7d079/pyobjc_framework_photos-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8eaa2ff3783f590d6906ce1b9b60f976c3473b17c805634f87927e07957b3888", size = 12268, upload-time = "2025-10-21T08:16:21.083Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c9/8296b98d4bc012d9666b350983b2e47e0b443466728c33977a8f1abe87c3/pyobjc_framework_photos-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:3689fde092ef4439167abf62ed2457889de7047d2d5b3b716054220451f3c4eb", size = 12282, upload-time = "2025-10-21T08:16:23.63Z" }, + { url = "https://files.pythonhosted.org/packages/50/59/2716769ef7dc1243f4548fd283d6c5fa6f06572b398f32ffa1e6852dd355/pyobjc_framework_photos-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:cd71c1eed83941e572467bd84ffed173def01fd898249e879972f4619dc67e72", size = 12464, upload-time = "2025-10-21T08:16:25.41Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e6/5e23437570bbaa7ffb972ce09281e98d2ca3d3ec6df145b428bb9835354f/pyobjc_framework_photos-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:bb524ccf20752e3c6cc7f3953b0272cc961a7a3a7312467054986d95db3a4ece", size = 12333, upload-time = "2025-10-21T08:16:27.024Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d8/67148c57f3554d242a270323e33e161c3e74bf877c2b62c95e241bc8f369/pyobjc_framework_photos-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:426c8149610e264b81f498bfd7916294e6d427449297346047c3328aad693701", size = 12522, upload-time = "2025-10-21T08:16:29.161Z" }, +] + +[[package]] +name = "pyobjc-framework-photosui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d2/73/7a9adf5eda2a5de6e40527531beb9a84fc2ca897a103528317c5f14423a0/pyobjc_framework_photosui-12.0.tar.gz", hash = "sha256:59bc6a169129b8a63fc5e175923900df4957c469081686299e2ba384291972fc", size = 30235, upload-time = "2025-10-21T08:36:38.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0f/b6/abebb883165e8bc64bc3664fadca366c3aea2a88cf1b054192719eee1ca1/pyobjc_framework_photosui-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e56f6834cbe6a0c470dc1c9b4300253c77c2694728322e0031c425a8195f34c9", size = 11694, upload-time = "2025-10-21T08:16:33.57Z" }, + { url = "https://files.pythonhosted.org/packages/b5/44/629979599411dc38fd3aae5f651e1726856ee903d641f7372008004f452f/pyobjc_framework_photosui-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:751e092ab34506d06657f22ee3c0db9c950ddc3435e8919b957f24529ef11dfc", size = 11726, upload-time = "2025-10-21T08:16:35.315Z" }, + { url = "https://files.pythonhosted.org/packages/06/d9/c746e5ef3caf2c6ce2e0a97a8b08f9acc050d83d86843c6dc68fb8bef8c0/pyobjc_framework_photosui-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b82ac86cb22ddc9dc3b113d52d7aedee268750ce61fc9edc54f07f0ab3092db4", size = 11730, upload-time = "2025-10-21T08:16:37.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/e3/fc7404f5c14e948476ba24fc593130c4527dae16ab733998ca977fc6ddc8/pyobjc_framework_photosui-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5350e303bbfdba0ead32e3215d9aaf70ea627626d38d24088e7a99bea5403598", size = 11934, upload-time = "2025-10-21T08:16:39.989Z" }, + { url = "https://files.pythonhosted.org/packages/5f/7a/7e82e472f8316fae6de43850a3a41dae9927404afe600399cf92dc5170b6/pyobjc_framework_photosui-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d3238e006d98d24c16bfd25583816f19ac4251841862e1b7e5aba53312497e83", size = 11733, upload-time = "2025-10-21T08:16:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f8/dd262e7daddaf97d90c00a992da820bb7a58c35e978e3db0a85f3351d63e/pyobjc_framework_photosui-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:33a83af5fe2864c83ff0ba76bed8cde6f4770fd71cb45f2abd3eb36d1eafec49", size = 11919, upload-time = "2025-10-21T08:16:43.623Z" }, +] + +[[package]] +name = "pyobjc-framework-preferencepanes" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/34/de/efe94e0c44a893893b8bac388a4a31d141f1fafa6085999cb09fd9dd1326/pyobjc_framework_preferencepanes-12.0.tar.gz", hash = "sha256:4c5a8df26846cada6c2cc7c1739d6b9334863a85cba509c3a62d92f13c18b112", size = 24630, upload-time = "2025-10-21T08:36:41.035Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/67/9ead9b61d31707d2c3ebcce7bbb019f2c469c1e069063d0dcaf76aa33a5b/pyobjc_framework_preferencepanes-12.0-py2.py3-none-any.whl", hash = "sha256:b9be4e2a69ad9809758b648b683438c3142f9803db6fab46a13e83ff31eff400", size = 4811, upload-time = "2025-10-21T08:16:45.044Z" }, +] + +[[package]] +name = "pyobjc-framework-pushkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/08/0407f3752efde2913268b31dc40003a0175088683353134b437476a3bd80/pyobjc_framework_pushkit-12.0.tar.gz", hash = "sha256:202f95172bf35427eb5284c0005d72ef8a9dc5aa61f369bee371e1f1f76a2403", size = 19840, upload-time = "2025-10-21T08:36:45.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/54/0bcba819c1e0ed1ca215e493e6736a441b1f065e66180158cfcd03c7c7b8/pyobjc_framework_pushkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a93d7250c135d517c398158a8316bf357a74b8015331731ac31c72462d19fa89", size = 8170, upload-time = "2025-10-21T08:16:50.664Z" }, + { url = "https://files.pythonhosted.org/packages/86/3e/1874e91099647791c56ecea1e6f23881e9c44058cd42d8bae0c4567879ce/pyobjc_framework_pushkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c0ff380dfc2b4cd67b7f84827cac4e2c947bb522624f385bde59945bf32c0782", size = 8189, upload-time = "2025-10-21T08:16:52.161Z" }, + { url = "https://files.pythonhosted.org/packages/08/c8/44baad8b36987b12fb37f939701cc1ba03c17be7f926c58a1deda8e4c0ac/pyobjc_framework_pushkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bcc6ecba8687123432900d62fa169cee2597515a960666b54e1d2e03db51b457", size = 8201, upload-time = "2025-10-21T08:16:54.259Z" }, + { url = "https://files.pythonhosted.org/packages/ac/06/213512593a6ed9432b626c3c24d88076e9cc713a0ac1518aa4d88ead6512/pyobjc_framework_pushkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:018caf2d8c19eb9d9bac771f97a854127eadae9752221f90f40f11067cebb739", size = 8348, upload-time = "2025-10-21T08:16:55.863Z" }, + { url = "https://files.pythonhosted.org/packages/05/ed/2a4013d9b1f7f504cc9add94b18f2d3879628d137ead61e3d5d7b27a69ee/pyobjc_framework_pushkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:24533a577d6d39b6ad6d9bbb659232d3a8d50e29df12cfc0a36938c4caf617a9", size = 8268, upload-time = "2025-10-21T08:16:58.413Z" }, + { url = "https://files.pythonhosted.org/packages/27/36/9c4651543ba426383d6aedcb8433d27d9285d176bd7b47fb42d77bd6b0a9/pyobjc_framework_pushkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:4c32316ccb304c72be565ecb8c1befea774876cf8e4cb40cfc2926402a4fbea5", size = 8403, upload-time = "2025-10-21T08:17:00.016Z" }, +] + +[[package]] +name = "pyobjc-framework-quartz" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/0b/3c34fc9de790daff5ca49d1f36cb8dcc353ac10e4e29b4759e397a3831f4/pyobjc_framework_quartz-12.0.tar.gz", hash = "sha256:5bcb9e78d671447e04d89e2e3c39f3135157892243facc5f8468aa333e40d67f", size = 3159509, upload-time = "2025-10-21T08:40:01.918Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/ed/13207ed99bd672a681cad3435512ab4e3217dd0cdc991c16a074ef6e7e95/pyobjc_framework_quartz-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6098bdb5db5837ecf6cf57f775efa9e5ce7c31f6452e4c4393de2198f5a3b06b", size = 217787, upload-time = "2025-10-21T08:17:29.353Z" }, + { url = "https://files.pythonhosted.org/packages/1c/76/2d7e6b0e2eb42b9a17b65c92575693f9d364b832e069024123742b54caa5/pyobjc_framework_quartz-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb6818cbeea55e8b85c3347bb8acaf6f46ebb2c241ae4eb76ba1358c68f3ec5c", size = 218816, upload-time = "2025-10-21T08:17:44.316Z" }, + { url = "https://files.pythonhosted.org/packages/60/d8/05f8fb5f27af69c0b5a9802f220a7c00bbe595c790e13edefa042603b957/pyobjc_framework_quartz-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ece7a05aa2bfc3aa215f1a7c8580e873f3867ba40d0006469618cc2ceb796578", size = 219201, upload-time = "2025-10-21T08:17:59.277Z" }, + { url = "https://files.pythonhosted.org/packages/7e/3f/1228f86de266874e20c04f04736a5f11c5a29a1839efde594ba4097d0255/pyobjc_framework_quartz-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f1b2e34f6f0dd023f80a0e875af4dab0ad27fccac239da9ad3d311a2d2578e27", size = 224330, upload-time = "2025-10-21T08:18:14.776Z" }, + { url = "https://files.pythonhosted.org/packages/8a/23/ec1804bd10c409fe98ba086329569914fd10b6814208ca6168e81ca0ec1a/pyobjc_framework_quartz-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a2cde43ddc5d2a9ace13af38b4a9ee70dbd47d1707ec6b7185a1a3a1d48e54f9", size = 219581, upload-time = "2025-10-21T08:18:30.219Z" }, + { url = "https://files.pythonhosted.org/packages/86/c2/cf89fda2e477c0c4e2a8aae86202c2891a83bead24e8a7fc733ff490dffc/pyobjc_framework_quartz-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9b928d551ec779141558d986684c19f8f5742251721f440d7087257e4e35b22b", size = 224613, upload-time = "2025-10-21T08:18:45.39Z" }, +] + +[[package]] +name = "pyobjc-framework-quicklookthumbnailing" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/64/3861655637e4beee4746e3f85af3f61028091d43f8b91fdff702285052b7/pyobjc_framework_quicklookthumbnailing-12.0.tar.gz", hash = "sha256:6b5ab7f8f75809535258c5af1db134e9f3449b36c5a40228766197527291297f", size = 14805, upload-time = "2025-10-21T08:40:04.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/16/da70d0c7aa6df70080e966e160fb0a545daa52a692c41a58cc659b6cdfe1/pyobjc_framework_quicklookthumbnailing-12.0-py2.py3-none-any.whl", hash = "sha256:6ff4dadb49e82319aa9391dbe759dc5d9fe3b7d30d87c6fb6efad22681c9426c", size = 4242, upload-time = "2025-10-21T08:18:47.341Z" }, +] + +[[package]] +name = "pyobjc-framework-replaykit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/a5/c2875fb3a18da6a63a574b9628b052c93cf32884edd77e951b67b5c79e5b/pyobjc_framework_replaykit-12.0.tar.gz", hash = "sha256:9b04f20b04e78e9a6e4d0e85bd5e706a02ed939e9012f468b16dfb6fcc3ab03f", size = 23686, upload-time = "2025-10-21T08:40:06.926Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/87/87a01c5cc5d515ac6dbd7db44f5906f905995b89ec9c1c7998898ddf3b4d/pyobjc_framework_replaykit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4137d25ae154c9c8f5ebbf16a8290b4505aebf32cf219a588d4d34e3ad24873f", size = 10102, upload-time = "2025-10-21T08:18:52.277Z" }, + { url = "https://files.pythonhosted.org/packages/1f/eb/8cbb645113ad566115a5984ccbeb8e5a2a07eec3a44df2d05d6fc912c9e9/pyobjc_framework_replaykit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bb4e68fc6bf54974da65acc6e0ae2ee2d6e312fd5a8b47c882bb4f32de0a1b62", size = 10132, upload-time = "2025-10-21T08:18:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/06/1d/a45705a7ac6ca4aec0329335f1531232be1ab9562029efbebfeafbaf9a30/pyobjc_framework_replaykit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e8d7ea4fe9a4ab2bfe9d9d166e81d1a449313784e9afcd25fa0eb5152520840d", size = 10147, upload-time = "2025-10-21T08:18:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/73/36/3483a6780a7078b42aa8cb6967f80e386efc12e438749454cb8015f303b3/pyobjc_framework_replaykit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:0409f253e632ab36edd86425737dfd695201078299172a40c662b3684b180021", size = 10329, upload-time = "2025-10-21T08:18:57.708Z" }, + { url = "https://files.pythonhosted.org/packages/ba/30/e4f9f62a3e0570d9614b70b2247d9f7f39432157b3e75457e16331649d20/pyobjc_framework_replaykit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3cbd3cc587e4c2fa722c444ebb5457568c3d0a803cf17cec107c9b6316a7539b", size = 10203, upload-time = "2025-10-21T08:18:59.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b2/b90f7451a313ff1d8f6fbc0f4d8c19c740910a45ab516ab1aab8062c1267/pyobjc_framework_replaykit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1e1cd0c2bdee7bf0eae66201c546e9e1093cfb5c365595a6fe0e0fc3bab3422e", size = 10397, upload-time = "2025-10-21T08:19:01.335Z" }, +] + +[[package]] +name = "pyobjc-framework-safariservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2d/90/ada857aca483a83dacada061746badb0d9eb705311df4c43139909eb8c64/pyobjc_framework_safariservices-12.0.tar.gz", hash = "sha256:3fa9624285723cb9df282479bee315f0548ee91e1a277d9bd767c273fa7648fd", size = 25499, upload-time = "2025-10-21T08:40:09.716Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/29/727f14374e39a737d3f520cbe873e95b41ea9905e58516b41c0a0084dde9/pyobjc_framework_safariservices-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:54d4ef4f7dad2e60a051f84a1bebff3bdc8efa302bbf2b3ee093ae8d8eb4778b", size = 7295, upload-time = "2025-10-21T08:19:04.898Z" }, + { url = "https://files.pythonhosted.org/packages/85/25/84aef5a0b1f28e769532759413b31bdbf02a0858c2c5d0834d93e7ec7a09/pyobjc_framework_safariservices-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ed9c9fefae246d282d81c71b068add82688a336b450e7981b970a27f684fbea", size = 7291, upload-time = "2025-10-21T08:19:06.421Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/2aac0cef66a560222cebbd9dd635b18292cb97c641415a590e248dbb58d7/pyobjc_framework_safariservices-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0a1700d2145fd5f1451cb18b7668eaef22fc2d099a5e5fd459e482c7b05cd0a4", size = 7310, upload-time = "2025-10-21T08:19:07.905Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/79d907a700357fd9d87717f65812d5280d96823f589b85f37c7916aae7ca/pyobjc_framework_safariservices-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:50513325180c950896cb242ce33c991bef87765e253f65ed583a442b29dfd243", size = 7317, upload-time = "2025-10-21T08:19:09.406Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d9/6d25774ce2090349bf6eee3bac285992bc8e91d8cd02c34b9a2770a875c9/pyobjc_framework_safariservices-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b390264fa1c262560e92280ac1d5180209fa382350e04a5bb29ea9dff9e78576", size = 7342, upload-time = "2025-10-21T08:19:11.279Z" }, + { url = "https://files.pythonhosted.org/packages/21/07/0ff0a95464871efa631ffd5a7155d5e4c7036c794df4618c99d493a898d4/pyobjc_framework_safariservices-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:792a6739a04cc71fc9a97ebd7c3df619320573ebd1e125a572302b592e7651ab", size = 7353, upload-time = "2025-10-21T08:19:12.77Z" }, +] + +[[package]] +name = "pyobjc-framework-safetykit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/ab/9038e5067650af29ffb491df5a02a3c45da0690e4a2efcf10640bde195a2/pyobjc_framework_safetykit-12.0.tar.gz", hash = "sha256:eec3d74db7a0cdc4265cd29def24b8f1af3fdace8e309640e68c58c935157296", size = 20450, upload-time = "2025-10-21T08:40:12.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/da/74/4275190d09a06e006f985efa7145fa64038c78e1c1ac736b850364e983c1/pyobjc_framework_safetykit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fbebcda5d29f0ba20762678b295b83ba40d9f017596b06fffc7575760de2ef78", size = 8550, upload-time = "2025-10-21T08:19:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4d/f76dff03599c87bfe264156ac9b2e34e8957d9a63ea0e438007e0d17203c/pyobjc_framework_safetykit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d378e53949c403879b73d43bd39e1bd60bd59db22625477633080d76c4ca2298", size = 8561, upload-time = "2025-10-21T08:19:18.223Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d1/e399f2c71934d4a07025374ed372ef459b1ed899bccba83e7c7d0d1e6833/pyobjc_framework_safetykit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:eee259b78a66b4b45aa84c7c8af26fbf8d1649fd39f3d9cb86b706d7b0ccf244", size = 8572, upload-time = "2025-10-21T08:19:19.853Z" }, + { url = "https://files.pythonhosted.org/packages/10/d2/9557ecb3fa41c2743eca6296139bdd4fdbcbee739ec83d629fe0fd0dd047/pyobjc_framework_safetykit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46e3c02c44cc0b7cd8398347b8a62761d6ba225201d0809228e2effbd512b7a5", size = 8730, upload-time = "2025-10-21T08:19:21.442Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5e/315677971eecc170c11beeb72735e5c6715c3975419417c0a3266153e0c2/pyobjc_framework_safetykit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8e2267cfbefdf123a44622dc0494b662d376bd3cb37629ada9f99aa83fdfc46b", size = 8626, upload-time = "2025-10-21T08:19:23.03Z" }, + { url = "https://files.pythonhosted.org/packages/24/f6/736c756819f5820072ba694584ea0037f25a9aa28836d1f806a40c45c8ba/pyobjc_framework_safetykit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d7e0b6e39e7c9e424b1ca9f470f5320ffb1988859bb6935b2d5388e9f55bb352", size = 8790, upload-time = "2025-10-21T08:19:24.719Z" }, +] + +[[package]] +name = "pyobjc-framework-scenekit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/6e/d67322896c3f0f4ae940d1a7a2ed49bdcad139d8f7ab2eeff066d2a4ca8e/pyobjc_framework_scenekit-12.0.tar.gz", hash = "sha256:3c725a9fa2f5788d6451291d1c71db9b68f1cbb1969facaa514cd6e73a11d7c6", size = 101580, upload-time = "2025-10-21T08:40:19.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/fd/524df6d6ca6b7f6877fd60c0403e73505a06e62aec2fa38f9f1df3f8cd08/pyobjc_framework_scenekit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41277e2893a0cdd620addc5c48a396ff9f2e499728ee77c48678537e26f47b6b", size = 33540, upload-time = "2025-10-21T08:19:31.436Z" }, + { url = "https://files.pythonhosted.org/packages/8e/78/b9505862a0a2ecb8bd07df489324cf6acc8f63b4a11ad6c3e1389e93ca94/pyobjc_framework_scenekit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:25e756f8e6c6747153238a2c6a799c40f1266becf75badeffe1b5a991f96bd82", size = 33598, upload-time = "2025-10-21T08:19:34.811Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/081508eb23901b8a05a3ce435d20402ade5f289336ef99069f753e3ed94a/pyobjc_framework_scenekit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:de17da992d7b17a3f2424ed05f2ef3bf745330cfc60a063bf3222ac734c5959c", size = 33622, upload-time = "2025-10-21T08:19:38.126Z" }, + { url = "https://files.pythonhosted.org/packages/12/3c/0e7e73f6d543558b85197d8805bbe6ac7ec3606780a51582b0485a72b398/pyobjc_framework_scenekit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b3cee34975b0bdcb87d1c14795ff5fa3a4c05d8332c9f35786a897e3610a2c85", size = 33937, upload-time = "2025-10-21T08:19:41.516Z" }, + { url = "https://files.pythonhosted.org/packages/b0/fe/d206308a63106ea829e9baf6e369c66097801f36e9cf17eee60856cdd60d/pyobjc_framework_scenekit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a5475e8508621749f957082a646761b8945391107d109c0bcbb13f4036d98c61", size = 33736, upload-time = "2025-10-21T08:19:44.787Z" }, + { url = "https://files.pythonhosted.org/packages/92/a4/6d5a47deda44661f643a355967857c332c49d1e42bb3ddd44ae5d46f777f/pyobjc_framework_scenekit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8cadd5d7ac9e3616845c4d5e9d5a0ac0117eb887e865d97babf5640f6971356e", size = 34018, upload-time = "2025-10-21T08:19:48.003Z" }, +] + +[[package]] +name = "pyobjc-framework-screencapturekit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4c/e5/6e1a3a5588d28eb7a80a2bd2feb8a76e32662ce169b309068121e94b0ea9/pyobjc_framework_screencapturekit-12.0.tar.gz", hash = "sha256:278743764adfbfc046b831bceaae2f0b4a42ea3b0b40e4ee349f9efcb62374e5", size = 32967, upload-time = "2025-10-21T08:40:23.005Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/06/ce09c0a558596063b9d903b2bf1ca25ab598929fcb5dbd266a47c2d3e461/pyobjc_framework_screencapturekit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cfb2f59776f80ae856b43a0dd3dc23dd79ea414f06106b249ece6f2fe37789bd", size = 11487, upload-time = "2025-10-21T08:19:51.749Z" }, + { url = "https://files.pythonhosted.org/packages/9b/1f/c06b269839eaa9efb8f5be0585daa2c5cb056f30df9566c1b9a71be23346/pyobjc_framework_screencapturekit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:07c1310f85bd661fb395895f13f1c69cdd5d83017e66c95e4daa122f97da11a8", size = 11512, upload-time = "2025-10-21T08:19:53.508Z" }, + { url = "https://files.pythonhosted.org/packages/09/50/e3809266ba4dbdf233cf4570d25eb9931c34e96db6cbb506ca12ec58de1e/pyobjc_framework_screencapturekit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c84a9051757706fff21d1f4b70a2255e53402c9b5d31f1708beac8c53237a9d8", size = 11531, upload-time = "2025-10-21T08:19:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/e6/55/3be7e77de7ae192d95e7e6aca39940457191c110cc4060b23bc328e69b62/pyobjc_framework_screencapturekit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:78392b27825eebd4afdf31b18d60a4e8d4a2f494af7ce6188c193f76f4142067", size = 11709, upload-time = "2025-10-21T08:19:57.766Z" }, + { url = "https://files.pythonhosted.org/packages/9e/39/ba12d780a0dc61985f00083f35ab3240c2f38feaf7a4854374fe2ec40ede/pyobjc_framework_screencapturekit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:1d95db1e63559ecb5472c4a90739c2282ac58694911a3c0d42ed22a0b381b322", size = 11587, upload-time = "2025-10-21T08:19:59.532Z" }, + { url = "https://files.pythonhosted.org/packages/34/d5/45b0fff308ffeb122400d7e9df81f15784da348bf3c2b56f504a47e376e5/pyobjc_framework_screencapturekit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:86bcc5c8d9243d16e675da7e8dd063f9afa18423f9b6c181754cf0624b84487d", size = 11792, upload-time = "2025-10-21T08:20:01.361Z" }, +] + +[[package]] +name = "pyobjc-framework-screensaver" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/56/8262f65fddc0e86f52f589d7ac927b7c2ee6fb9b83c5906126a7544707b5/pyobjc_framework_screensaver-12.0.tar.gz", hash = "sha256:d1f875a89c511046d08304d801aba960e9ceef62808de104bb878d948696d29b", size = 22614, upload-time = "2025-10-21T08:40:25.795Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/db/ba6dc945e1d0ac1877888fe9d425db98d7f73c0f52beaa401d9b0a3ebc1a/pyobjc_framework_screensaver-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:724713c35f7ff2c1ed1f2ed6785e7872ff14de74a36538fbedfae5eb1ab1b761", size = 8496, upload-time = "2025-10-21T08:20:05.464Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d2/0d91b21eaa6f5d9d80ee960b3d6322b1c84d840bc152770ee6865734b020/pyobjc_framework_screensaver-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:176854fe9787bc431c7c5e6cfa7e6d6714fc49e189364cc2cd6ce27b8c24c21b", size = 8440, upload-time = "2025-10-21T08:20:07.109Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d6/4181e31c3b87ab480bc3ef44e456d1c20e7d53e15b1d00a686bb459150d6/pyobjc_framework_screensaver-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:87de6e035315b6b2304f20a1953b5c3c6c017f4ef73bc91a4fd23a1789f4cc2f", size = 8457, upload-time = "2025-10-21T08:20:08.744Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d8/80e00cfc6fa2766f324c2fac4a882e82a6f1ebbbfddf7c5bee6aca933d94/pyobjc_framework_screensaver-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7ae3fae60a3740f73c4267e1eb0e430d064d1ed56b84fc4e8aac7fe4b1fdbbe4", size = 8463, upload-time = "2025-10-21T08:20:10.367Z" }, + { url = "https://files.pythonhosted.org/packages/8d/ae/c869b82f2a10985d9091581364c185a66cf770c0b923b6546b372981a54b/pyobjc_framework_screensaver-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:dac0a57ad4c39d6ff577c5a8e776f53654e29022096bbbbfffe73575c1d3fdf3", size = 8498, upload-time = "2025-10-21T08:20:11.954Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4e/0c90bf65c4166fb976cad68e18811aed9fbc8167bfce51cc4edc31233dc2/pyobjc_framework_screensaver-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:163621994011fd25b2d48bacbee45ffca8b0b2e4726bf8d7692ef969e2222545", size = 8511, upload-time = "2025-10-21T08:20:13.528Z" }, +] + +[[package]] +name = "pyobjc-framework-screentime" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6c/0a/369431b09cd9cfff0c6be01e256244d446ae8d37d95bcd8b79191078d5c3/pyobjc_framework_screentime-12.0.tar.gz", hash = "sha256:cf414fcb988b4ca408c82e1924f8ad9b52f3ff6d509a9dec5eb84983e1cd45bb", size = 13444, upload-time = "2025-10-21T08:40:27.696Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/fc/974228e9a93ad848f585ba74be4b0632ef18e652aa7459553a1490ffd276/pyobjc_framework_screentime-12.0-py2.py3-none-any.whl", hash = "sha256:c8046559698a53b7dfb7e7515fcfe5df850ffa0f6c093b5d825b5446af7e8604", size = 3975, upload-time = "2025-10-21T08:20:14.98Z" }, +] + +[[package]] +name = "pyobjc-framework-scriptingbridge" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/ff/478ce8ba77b61b9b48bf2f881f0aec7c6059eb9166e29c6ee60223b09cb3/pyobjc_framework_scriptingbridge-12.0.tar.gz", hash = "sha256:062f03132fbf2f4e71bcf80d7e78c27d63588a1985d465ab1e7fa07f806590b5", size = 20710, upload-time = "2025-10-21T08:40:29.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4a/10/02af88fd86af17661bdff02362fe4ba9b933a3dfd16344004298fb7ff6b6/pyobjc_framework_scriptingbridge-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f868ad91d15b6e016dfa636a8f16fd12a5ff99fbf7b84280400993b5b24cfe0f", size = 8343, upload-time = "2025-10-21T08:20:19.016Z" }, + { url = "https://files.pythonhosted.org/packages/0e/49/06868e9cc7fad44fc16fdb5b36764628a0cd5afcf56fb10e37601ab4b34d/pyobjc_framework_scriptingbridge-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a1ef5b16ed385166927df61f66fab956453f0c08a82c9260cb0d0c54a7d2b63e", size = 8365, upload-time = "2025-10-21T08:20:20.627Z" }, + { url = "https://files.pythonhosted.org/packages/4b/53/aac8e25857219614b173028d34ee0d2a816f3b9d81e9c93576ee39f79f94/pyobjc_framework_scriptingbridge-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:453ae60ac93a7e183853715b6b4ede6f4cd581e1c008011820db0216590d60e1", size = 8380, upload-time = "2025-10-21T08:20:22.562Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/1cb8a408f7dd79696cb6cdce82e4e0f80179f975a56a15bf051d85c429c6/pyobjc_framework_scriptingbridge-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:74a8d2d009c075f47b38b88767c84626865fef29ddf94c5e01eac4b165358b27", size = 8529, upload-time = "2025-10-21T08:20:24.575Z" }, + { url = "https://files.pythonhosted.org/packages/94/ce/ce8c048050770f416c7b385a69e24101b4d4ced53dee836fbbdcac24515d/pyobjc_framework_scriptingbridge-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:d70baa98108d4165a4dad62ddc30174fe7811b1425d99ebd9267e4d2d13ab549", size = 8412, upload-time = "2025-10-21T08:20:26.594Z" }, + { url = "https://files.pythonhosted.org/packages/3d/26/7395fd8bee832a665f94e4d97cb8c9dd679c1c4e4159a5f54c33c5c21cd3/pyobjc_framework_scriptingbridge-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f6b1d24381e445a815e6b2a7d4c00a343912aa549b8b781488652b072166f00f", size = 8572, upload-time = "2025-10-21T08:20:28.378Z" }, +] + +[[package]] +name = "pyobjc-framework-searchkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c2/28/186a8525adb01657e2162ab8cd2ea3df17201bd1def22f460a6838301ca3/pyobjc_framework_searchkit-12.0.tar.gz", hash = "sha256:78c5fdd8f96da140883eabca82a3eb720a37e6e58c9a90d1c62dbe220a3fded5", size = 30949, upload-time = "2025-10-21T08:40:32.868Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/00/e56077f1e21d55772064b645bd0b9359747967e9cb4599c48f79d3c77b99/pyobjc_framework_searchkit-12.0-py2.py3-none-any.whl", hash = "sha256:12dd4a566df2616dad316c95eb5b77fe7f98428a8cb707aee814328ce07bd6a8", size = 3742, upload-time = "2025-10-21T08:20:30.024Z" }, +] + +[[package]] +name = "pyobjc-framework-security" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/d6/ab109af82a65d52ab829010013b5a24b829c9155bc9608ebc80a43b8797c/pyobjc_framework_security-12.0.tar.gz", hash = "sha256:d64d069da79fbf1dadbc091717604843b9d5be96670f7b40bc9a08df12b4045b", size = 168360, upload-time = "2025-10-21T08:40:44.379Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/59/b7fecb01ae93980a93bfb027dddc793b58f39157b5e740972739404f6450/pyobjc_framework_security-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:39b0b5886b1ed0bc38a21d98d3b1be948ab9e6ca5b9e52261f8aaae9214ca282", size = 41302, upload-time = "2025-10-21T08:20:37.789Z" }, + { url = "https://files.pythonhosted.org/packages/b5/81/847a61699c4c3def381b498aa3e6bd9d134dc610587f4ff29eb912014390/pyobjc_framework_security-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1d7a157927d1d90b884a602a32f324798fcc6c29241e7d1057216104a4fefc85", size = 41291, upload-time = "2025-10-21T08:20:41.412Z" }, + { url = "https://files.pythonhosted.org/packages/2c/6d/7e50349ed08cfd2ee7438642b51512415739a87befc009d73b026d1e35c1/pyobjc_framework_security-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:be1435584cdd116495a16e6cd8a086d6930f0005ea49df4e4958b5a142dd6f63", size = 41291, upload-time = "2025-10-21T08:20:45.044Z" }, + { url = "https://files.pythonhosted.org/packages/2b/4b/4bcc8a24806fb5cabd81b0c9bd110ec559eccce55829754f7a88931c2cd2/pyobjc_framework_security-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e3c27816b102858c976956ab8eee156b9c724cd0f1d488f3285ac4921a904788", size = 42167, upload-time = "2025-10-21T08:20:48.651Z" }, + { url = "https://files.pythonhosted.org/packages/51/b6/aabbb1ef3268b487f36caf5647a0f544ae0ab32518f70e622821f2030d9a/pyobjc_framework_security-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0f9c1598215a9372f446e63ac5dab8a120e25f3caa5890b2abd8b075e4122a52", size = 41362, upload-time = "2025-10-21T08:20:52.26Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/aca4812e4d619c667f8432b79142cf6f89f7149aaec2194fed1f8b211da7/pyobjc_framework_security-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d67224e548735f4464778f1911063fd37b64dfe3950d0920d9c1afac229b03db", size = 42918, upload-time = "2025-10-21T08:20:56.1Z" }, +] + +[[package]] +name = "pyobjc-framework-securityfoundation" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b5/f8/b806f00731237ef45d7cf6fdb12233320696e23e6bd04b14932027a03c81/pyobjc_framework_securityfoundation-12.0.tar.gz", hash = "sha256:55890147e294c5eb92f2467111ae577d18f15710ff3bb9caecb961b8397c5708", size = 12728, upload-time = "2025-10-21T08:40:46.366Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/93/d0/ececa41a50918594b8ee3f28af4174fb47740950e758585bc70c787f49b1/pyobjc_framework_securityfoundation-12.0-py2.py3-none-any.whl", hash = "sha256:01933f6f5424e11e19e833803b65873458d3a32de390f8c6bfa849e258f0c018", size = 3803, upload-time = "2025-10-21T08:20:58.011Z" }, +] + +[[package]] +name = "pyobjc-framework-securityinterface" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/3b/0d263da7f2fa340e917b5a003d7dc34f930a60b4d489bdb29974890860c6/pyobjc_framework_securityinterface-12.0.tar.gz", hash = "sha256:6a17854bb37737b14684b379f2e3a7a71e4f2e5836aa3cdff7e9c179fc65369c", size = 25966, upload-time = "2025-10-21T08:40:48.931Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/9f/32b7a098b68ebda130ea3f2cbf5505fe8b52b9a3951b4731a5c537479429/pyobjc_framework_securityinterface-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41e3dacb1616490fca4c20ab7375386554bb4fc8836fa1f691fdfd062bfa4f4b", size = 10728, upload-time = "2025-10-21T08:21:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/55/17/76ce2b4dbd96821895991484f95ed08a6c08df471dc9c2d05e80cc5c83cc/pyobjc_framework_securityinterface-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ff4a60b98f53f3a38e4f9276a1ae98710800164bf13fe13097e90d229ae0367a", size = 10791, upload-time = "2025-10-21T08:21:03.346Z" }, + { url = "https://files.pythonhosted.org/packages/06/fa/941e19d267f38bfe0f714bce99af4f180e55868bff881e5dab5dcc1b1dab/pyobjc_framework_securityinterface-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e21a47d9ae3fdf7baa7c29c4ce3cc4abd3e3a7a6f7926fa9823343374cfa8d0", size = 10807, upload-time = "2025-10-21T08:21:05.002Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cd/feeaccb7c9f38f40cffdc444ad7686343e11ec609431ed72dad54b833456/pyobjc_framework_securityinterface-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c138276a669e796f1d49053cd5cedabfc6eb911cd0a4e3ca7665251adf37ced2", size = 11144, upload-time = "2025-10-21T08:21:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/59/6f/2703523d2cd838ded70ba1022fe7f8012c265ec7c896d7def302274dd1b9/pyobjc_framework_securityinterface-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:658932a843f569ea40a2a3f9304fac0dac42ac37eb28e8e072abdbe6239a5943", size = 10844, upload-time = "2025-10-21T08:21:08.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/6a/a8a7b6301436bf4b900aaca3ed1ee752d2da0bf6214aacf1315f25da5bf3/pyobjc_framework_securityinterface-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:0fb1214d7d25ac1eb2892d0c6a9ab5295cc1084e291b4c79b0c97279cdd2f389", size = 11194, upload-time = "2025-10-21T08:21:10.501Z" }, +] + +[[package]] +name = "pyobjc-framework-securityui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/b9/40ee5e3added96c9b2039e5016b7a994783c09580ac89eb5f077b9ed8810/pyobjc_framework_securityui-12.0.tar.gz", hash = "sha256:cbb5cfdb5f196ecb5b1c7369fa6af6e8a3c285013c8949b855b39bea4c09382e", size = 12206, upload-time = "2025-10-21T08:40:50.736Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/82/53bacd8fc7344bbce297f317f9a46ea0f4c75f9cdd3c72bc6b0b762b440e/pyobjc_framework_securityui-12.0-py2.py3-none-any.whl", hash = "sha256:9c7511241d19b416b79b1291eb57896ffc317528e6c342982722a32901a177a5", size = 3606, upload-time = "2025-10-21T08:21:11.839Z" }, +] + +[[package]] +name = "pyobjc-framework-sensitivecontentanalysis" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/fa/1a597c43747efb764f8d069b4d8db0458cdf14086ce9bd32fa41139484e1/pyobjc_framework_sensitivecontentanalysis-12.0.tar.gz", hash = "sha256:2e56f19af4506a0b222b223f70ab59725fc59b24d40267c1e03dcd3113f865ea", size = 13786, upload-time = "2025-10-21T08:40:52.907Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f4/0b/3be629ba18bec304236dba34e7bc592faa6a8486dd1188bd3994102ea2ec/pyobjc_framework_sensitivecontentanalysis-12.0-py2.py3-none-any.whl", hash = "sha256:fca905676790e76a2697c93fb798479aee3be5a57144ac681fa0e5cdc33e7d3a", size = 4240, upload-time = "2025-10-21T08:21:13.355Z" }, +] + +[[package]] +name = "pyobjc-framework-servicemanagement" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4a/76/8980c4451f27b646bf2b6b9895f155c780e040cfdddc66a3aca0125b93bf/pyobjc_framework_servicemanagement-12.0.tar.gz", hash = "sha256:768e0a288f38a4dcc65bbfc144fbccfc10fc29df72102b1a00923d78385d1c15", size = 14624, upload-time = "2025-10-21T08:40:55.084Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/c0/dc4c35cd42fc6e398d2b86f05a446007d3ae802cda187b8cf6834c3a248f/pyobjc_framework_servicemanagement-12.0-py2.py3-none-any.whl", hash = "sha256:57c22bb43aa6eb956aa5dee5976fe8602d45b72271e9ae9ed6f328645907fdac", size = 5366, upload-time = "2025-10-21T08:21:14.996Z" }, +] + +[[package]] +name = "pyobjc-framework-sharedwithyou" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-sharedwithyoucore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/49/9fdb0d4e8c1f2d800975fb60d6975292767379e37250360072d9d84e9116/pyobjc_framework_sharedwithyou-12.0.tar.gz", hash = "sha256:e83152057aec724ede34be680bd98d5962b2e5d5443646fe41635fda9d5e996f", size = 25148, upload-time = "2025-10-21T08:40:57.485Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/f5/49794fdc63f17f58b9cc9f6d3f7a851c0397c9bb8a1472d0ff8a1e18c1cd/pyobjc_framework_sharedwithyou-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dd6073e3371d208d30617a94c1ae93e097c77f253a49daaa2511e0e408a8f73c", size = 8756, upload-time = "2025-10-21T08:21:18.308Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/7e00f13185d1275a57297c436f956b0192252d26d871a66cb036aea56594/pyobjc_framework_sharedwithyou-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:988e16bf4f2e440cf5c18d377d17314e10e52fe1c6f528af23fbc2914b26a1ab", size = 8774, upload-time = "2025-10-21T08:21:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/bf/16/ddf19adbbc69e57d484a683aaa1c1812da1a732188de75ebdc97c0c25f0b/pyobjc_framework_sharedwithyou-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c03665432b090e4a147a30f1af936a259ecf0ce337fe534ceff2c4f46dd12524", size = 8787, upload-time = "2025-10-21T08:21:22.613Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/1644c078321e73a769054744186930d639e38be99b9369da2004993a292d/pyobjc_framework_sharedwithyou-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:25f403f90688f2b4f389d1df4902ebdee59bd5c44861cc04d217d513b1c7d9b0", size = 8932, upload-time = "2025-10-21T08:21:24.542Z" }, + { url = "https://files.pythonhosted.org/packages/9e/77/a54b13ec4d1dfc3d6b9c12393b61e40fcb56f096f4bf119d66244a3a149f/pyobjc_framework_sharedwithyou-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:3509163025f9a47a366d22472fc7206c509c32019a6b9c9c520746df70e34f95", size = 8831, upload-time = "2025-10-21T08:21:26.155Z" }, + { url = "https://files.pythonhosted.org/packages/8c/94/4c09b390fb4b8f8ee19072ddb19cada38e7ea4ae2e6c63a6276c22bfd4c9/pyobjc_framework_sharedwithyou-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ace608ae20e48fdd082426c560d9bb558199256b69653b8e688f723d6eb6e012", size = 8980, upload-time = "2025-10-21T08:21:27.784Z" }, +] + +[[package]] +name = "pyobjc-framework-sharedwithyoucore" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a5/da/6e2f57bcfd4a5425a97d98c952d92f55c2ba8e5b7b227b2c122af9ab68f4/pyobjc_framework_sharedwithyoucore-12.0.tar.gz", hash = "sha256:ea923c3336c895d3dd79fa405f6fc17db6abbaac85ed8d7ed4ce9887e508ce1a", size = 22791, upload-time = "2025-10-21T08:41:00.157Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/99/46/366371e82b7d6d5b5185442be27b251a18b2a49c81ba873d9831c2a4fa41/pyobjc_framework_sharedwithyoucore-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a886bc070964b2693bb6575c60ea8b70446995b6dea18db3293b183349d68846", size = 8522, upload-time = "2025-10-21T08:21:31.189Z" }, + { url = "https://files.pythonhosted.org/packages/91/25/c759f4764b31a4adefa664e58b169e9ca23e73ff24450600338e5b264e8e/pyobjc_framework_sharedwithyoucore-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d54acd83c19d9fdd8623c4794906fbab24b2f02be2c77f665ceccbd5cf320b8d", size = 8543, upload-time = "2025-10-21T08:21:32.802Z" }, + { url = "https://files.pythonhosted.org/packages/2c/be/53a568fb87f037382f1ff87df03d393b529cb6fcebb1506c4e6cf8a0a1f8/pyobjc_framework_sharedwithyoucore-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b51a3ac935dd41d0d4ebe5ac08960e4a91e0732e94cf4bca0f753b86f6b79bf0", size = 8554, upload-time = "2025-10-21T08:21:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/69/4a/26177b557b8f9a4cb7d95984c5dd06d798bfb3dc64adf10f71af8eb6a424/pyobjc_framework_sharedwithyoucore-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:84cc03cfd3e0dada72991f1c842ab16176a4bb859a20734a9aa30a6954978305", size = 8687, upload-time = "2025-10-21T08:21:36.042Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1d/7c85af279ba24427ef6e4165cd22d99690ee69700703116243a1f9b38038/pyobjc_framework_sharedwithyoucore-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:14e2ef808e72628e037b5967b196470f5dcec28931d81451d49b30aa87591310", size = 8600, upload-time = "2025-10-21T08:21:38.002Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c6/ecb7332a7d6d23b883c3cedf7607a6c7d984074cb5eefc0c17ea927ae820/pyobjc_framework_sharedwithyoucore-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1705dce361b984dea4ba1cb2e67f3433cf4f074cbf49729e8999254726896c04", size = 8749, upload-time = "2025-10-21T08:21:39.629Z" }, +] + +[[package]] +name = "pyobjc-framework-shazamkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/21/1743b7d7592117f9739f0c14041e90c5de28b05a8b0c936602719b624fd4/pyobjc_framework_shazamkit-12.0.tar.gz", hash = "sha256:4624fc90435eaabb19c0079505a942e92b6cdf516830340289d543816fceca91", size = 22935, upload-time = "2025-10-21T08:41:02.444Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8e/91/dc1d060770503d0a6bbafbc49d2dd5dd75d4fb7342b8ba8715dd4259e333/pyobjc_framework_shazamkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e5dfdfbdb598f59a29ed30419327bd9eb3ac9daa9eca7e3f5180e0034510fa8", size = 8562, upload-time = "2025-10-21T08:21:42.954Z" }, + { url = "https://files.pythonhosted.org/packages/76/0f/adbc22ad35a32f74cf097d7e79e7980fa055c04a414fcf50d6d620f49821/pyobjc_framework_shazamkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70b96018ee5883febe4389b740cf78e5412ad1386467b7122a10db20d19d2773", size = 8582, upload-time = "2025-10-21T08:21:44.645Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e8/05e934e4f36432c191ab662056ec1807c26a7f56f02de7ac151b244432e1/pyobjc_framework_shazamkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:50337b0e81d51f07beef7db7b036b2f2051ea0603f0d92ff93f8596d67f6dba5", size = 8595, upload-time = "2025-10-21T08:21:46.576Z" }, + { url = "https://files.pythonhosted.org/packages/e2/7f/16e61fe1fae03f2f4bd81b6e328eeec78d5c6cd18dc8d1762deafbb8274a/pyobjc_framework_shazamkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:3f074540562a0de1e2dcb66f70a74ab73035da475f9c3ae4426f91fab8c5af35", size = 8738, upload-time = "2025-10-21T08:21:48.159Z" }, + { url = "https://files.pythonhosted.org/packages/ea/53/fa4bcde1af718ff832825e167522ff7e18ce03b11f27e55638fc3f312239/pyobjc_framework_shazamkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0911efc4dafbe1fbb8d44acba01b2473efb9bf5c49f7a6899cfaddc441298fef", size = 8656, upload-time = "2025-10-21T08:21:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/04/a4/9be04728b6483b1ed47e81ed4ee4059a0e84a06d36084d18aa6239728bac/pyobjc_framework_shazamkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:ba5089661647e16978e29a43ebfba96f713cae1eb9dba270719598516b8c2dcd", size = 8798, upload-time = "2025-10-21T08:21:51.435Z" }, +] + +[[package]] +name = "pyobjc-framework-social" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e1/a0/034973099006522f01a32f83cf29458bd89acbd4b5a7f782358c9d781bf9/pyobjc_framework_social-12.0.tar.gz", hash = "sha256:be7d4b827537de49dea96c7defcfd28263b4a4cd4f28c5abeb873a072456db5b", size = 13229, upload-time = "2025-10-21T08:41:04.277Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/dc/4da2473821c80acbfa65783430faad8923a0281e257960e5abcc821265b2/pyobjc_framework_social-12.0-py2.py3-none-any.whl", hash = "sha256:0bf4b935014f70957d0dd6316ce47c944495201c30990738d9be11431fa0db00", size = 4469, upload-time = "2025-10-21T08:21:53.037Z" }, +] + +[[package]] +name = "pyobjc-framework-soundanalysis" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/90/eb/30927f7d3e93913fcb4472bd2fb46b90cf341a52065c4c3bad3ffac463ad/pyobjc_framework_soundanalysis-12.0.tar.gz", hash = "sha256:eb60a6b172ca2d71f8b5ae9b6169a3b542755af0f763fec0786403f90b1394c5", size = 14871, upload-time = "2025-10-21T08:41:06.236Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/15/2a/80786fe9e85ddb3b44828336911bd4bab99a2674cf9dd7912295f6c319a3/pyobjc_framework_soundanalysis-12.0-py2.py3-none-any.whl", hash = "sha256:08fd2e988ca0ae84c8dbaf490d634e250d32e44f420de7e6c2ff72bac947aaaf", size = 4197, upload-time = "2025-10-21T08:21:54.618Z" }, +] + +[[package]] +name = "pyobjc-framework-speech" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/73/623e37a98f0279cf4e5b6c160bcf8b510bb67d4f9fdc3202b48c326bdc66/pyobjc_framework_speech-12.0.tar.gz", hash = "sha256:9e6a208205e3065055e3d98b553464086ddc60f165df7e9c93596a819b4ab9b4", size = 25615, upload-time = "2025-10-21T08:41:08.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/63/995dbdaafa2f15d1f8a0c267588ff2d3c724c2484a3f79f5819a475c7df5/pyobjc_framework_speech-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32aa8a1c357e2519da3047873bff1cce385c8603c58b58e10ee88428440a44f2", size = 9258, upload-time = "2025-10-21T08:21:58.41Z" }, + { url = "https://files.pythonhosted.org/packages/31/51/6adcaf102696516c9bab1f89a13762030cbb21b952b3ac01509238bdcc51/pyobjc_framework_speech-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2c84614eaa280af3a3a294afe94e6c8b47ada81a7b9cedd218ca5d2ab23d9e5", size = 9262, upload-time = "2025-10-21T08:22:00.022Z" }, + { url = "https://files.pythonhosted.org/packages/d8/39/30c9e02475afd3976c3667cfc5a94aaf0237579d1f9b588292706299e38b/pyobjc_framework_speech-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f5c81f0c5e32110f61fb487d3a47d4fc504776ac2d5ab2a9857a7ebe921fbf1d", size = 9280, upload-time = "2025-10-21T08:22:01.728Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/c41931ca8e305bd250e7cc7adbfecebefaaa296b06d0c1d1dbc87d6266f3/pyobjc_framework_speech-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b3baea1720e54a60bec2ce20d7b979fcfe25d1e25f2e2a4ca4e5b23a990b210e", size = 9442, upload-time = "2025-10-21T08:22:03.701Z" }, + { url = "https://files.pythonhosted.org/packages/16/89/b9c6fbbb2adbb42005884b8294899b994d206d299d6c826c55f8bdf20d08/pyobjc_framework_speech-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:71fd245edc11cbbe890772cd4a8bfa48ade5fa83dc5e5add1a10882a21b3182d", size = 9345, upload-time = "2025-10-21T08:22:05.671Z" }, + { url = "https://files.pythonhosted.org/packages/7b/cd/5ffff71717caf90e6d5f95a0c38fa68496a341e75315fb9a0d91dbb5ba25/pyobjc_framework_speech-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a2a971db829b76c9b6377250d9a406e8ad50d81c0e13ed9831ba429375570732", size = 9505, upload-time = "2025-10-21T08:22:07.666Z" }, +] + +[[package]] +name = "pyobjc-framework-spritekit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/a0/aababd3124b2303379d76dfd058b2c37d1609e6397f932a183dbb68b2d31/pyobjc_framework_spritekit-12.0.tar.gz", hash = "sha256:d2d673437d5863f59d4ed4cd1145c30c02cf7737b889573252d8d81cbb48e1db", size = 64834, upload-time = "2025-10-21T08:41:13.859Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/e3/6aa92eaaa6e3ea9cad1a575229cfb3e47ec8089f24922be7e4f054af54c8/pyobjc_framework_spritekit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d0ad45adcdf1d1051f9f3931f01dd2728953ae5d57d517de12336399633640fa", size = 17749, upload-time = "2025-10-21T08:22:12.372Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c6/85d89adc7ed775716e4dfb0bf2ecb72fd5c11bbbed5da524bfe04df2bade/pyobjc_framework_spritekit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c34305a13f3c7d999795b44cb71501b4c812a94fa542ab03ed9cfcbe8c52ec6d", size = 17812, upload-time = "2025-10-21T08:22:14.465Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f4f69dee686daa9bc69cc09493b0fbe642db7fac6a1eb3daf8cb8b1800c5/pyobjc_framework_spritekit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a67878483326b8079e6077ecdeb571a91197b7f13a1aab803cbb14d0e966ffb6", size = 17828, upload-time = "2025-10-21T08:22:16.559Z" }, + { url = "https://files.pythonhosted.org/packages/14/03/cdced6f888211515503ccafcf9d46ae34ad65cbd44286be7e1bb239d5517/pyobjc_framework_spritekit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e460d1b764755a7e4bdeef79ffc66d016c496b0a20ad679ea2cf2ec4ced13af9", size = 18096, upload-time = "2025-10-21T08:22:18.692Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2c/078f283220713936774d6bfe3ae05e57303fd9fe64103a453a5423a95938/pyobjc_framework_spritekit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:0bb3d5ccec06f3165f5c8eae891a9a5e218bbb28a19f661b300340b1d71fde19", size = 17800, upload-time = "2025-10-21T08:22:21.199Z" }, + { url = "https://files.pythonhosted.org/packages/16/de/0ab2c08e12a21cb8a94bece9069002f77a49cca5c825797840a8a78fccc0/pyobjc_framework_spritekit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f404417bfacb9702a24b706cd6376b71e08980df13d2d808ff73dab0027dca4f", size = 18079, upload-time = "2025-10-21T08:22:23.704Z" }, +] + +[[package]] +name = "pyobjc-framework-storekit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/a0/c8d7df4eb7f771838d6075c010b11fdf9d99bff2a60261b03ed196b22b03/pyobjc_framework_storekit-12.0.tar.gz", hash = "sha256:b72cbf8d79fa2f542765a9ccd75b3fc83ed0b985985c626e09ea268246416a95", size = 35012, upload-time = "2025-10-21T08:41:17.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/5c/fefc599ba997fdd3551a3d4cffcd7344057a4bff2017085942bae074339b/pyobjc_framework_storekit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13c5e3466a2388c6043c6fd36f0602d5e34bbfd1f2bce4a66e06f252ac5158e0", size = 12819, upload-time = "2025-10-21T08:22:27.723Z" }, + { url = "https://files.pythonhosted.org/packages/42/78/6d860fc737a446549e1472586a3800b87d9a88b420afe207e902708df595/pyobjc_framework_storekit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a05abcbd36d7adf82f84257a6fb0edf763eb0c57dcef987a3306e79099b8988", size = 12834, upload-time = "2025-10-21T08:22:30.014Z" }, + { url = "https://files.pythonhosted.org/packages/87/48/ed3822fa87e96a0724b05e212f7e0829dc8739e44f4adccc8fc85f0b08bc/pyobjc_framework_storekit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:961dceeeb3ba3364b1fc77f2176cd6fcff2e19fef2eb402b14bdef616ed7a121", size = 12845, upload-time = "2025-10-21T08:22:31.909Z" }, + { url = "https://files.pythonhosted.org/packages/d0/1d/0d473466153c1d651d0ed4c139556d8ae8c7029bcc5603154e37ffd0b6d3/pyobjc_framework_storekit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a87d636a2c7d905b9e429a4dd30ffd5dc895539da11ba282c5bb0a47781503ae", size = 13036, upload-time = "2025-10-21T08:22:33.78Z" }, + { url = "https://files.pythonhosted.org/packages/fc/49/2a2c7177a8f8543473b5b0c1c6a658689c59d2274a77ec1537a69f083b44/pyobjc_framework_storekit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:a682be4a5c896a916bf4b7e976c343e8ba81d0f301cc23bad93609f9bdbadff4", size = 12833, upload-time = "2025-10-21T08:22:35.662Z" }, + { url = "https://files.pythonhosted.org/packages/60/be/5dc4eef2ba8f81cdcebe654d691709e5cf37d94ce67b532a6e4d76e023d3/pyobjc_framework_storekit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:9afb63e5b13fc60a4f349d9816e4a9670b79a38984bab238f956ce062cfaf856", size = 13027, upload-time = "2025-10-21T08:22:37.576Z" }, +] + +[[package]] +name = "pyobjc-framework-symbols" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/49/7e206fa8b912bd929bbcae17627f370ac6f81c75c1d2ca3a006fb12f4697/pyobjc_framework_symbols-12.0.tar.gz", hash = "sha256:0707226ae8741163f3f450559c7d7c87a987ddb84ccb5fe22fb1f40554404cfa", size = 12843, upload-time = "2025-10-21T08:41:19.35Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/eb/bec85c6ca8b765ff135297ce91acee1a63fbed8a9a5ad130dfb46e2ee50e/pyobjc_framework_symbols-12.0-py2.py3-none-any.whl", hash = "sha256:e47998c35073906cc5c82ca1eff73957d9f2b673621bad044cfa46b0b08697a6", size = 3345, upload-time = "2025-10-21T08:22:38.927Z" }, +] + +[[package]] +name = "pyobjc-framework-syncservices" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coredata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d8/41/c7a6c68a0ceb7309ee4e167396a1d806543d7863a0e2945a835fd463359c/pyobjc_framework_syncservices-12.0.tar.gz", hash = "sha256:7ba335196f09495fade38753958ce5dcabe25a1280821ac69a77a1fc526d228d", size = 31454, upload-time = "2025-10-21T08:41:22.26Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/ea/e821da8003286fe2cfa9bd5df3b79311d5e3a347db9fed8e8e1f4f8326c7/pyobjc_framework_syncservices-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:00895ca29cffb71351affe0fec2ee849c40411ed0a81116d82acfc064403d781", size = 13390, upload-time = "2025-10-21T08:22:42.854Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/590615681bdf2933a914f6f28a97c776a88e99aacbb907345c762e322335/pyobjc_framework_syncservices-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e6c258ad36e89b70ff88ab389b825cd29b78a664dbee0fd22cac73eb0e448c4e", size = 13425, upload-time = "2025-10-21T08:22:44.818Z" }, + { url = "https://files.pythonhosted.org/packages/53/70/acedc33df3d03aa1638f854de91c08cbcd1ae844111033aea1b58a7b8ee0/pyobjc_framework_syncservices-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e9c5565ed72d4bce4e51a810c3fc72d3a9f19f6554fd9890fe3864c6c93220c8", size = 13436, upload-time = "2025-10-21T08:22:46.811Z" }, + { url = "https://files.pythonhosted.org/packages/b3/3b/bcc45794a73cc1bd4c5d9fb9505686d7b60e32ba09bd6af2b8a94b5de18f/pyobjc_framework_syncservices-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a462940649c6823aae889c330c748aca4dca96d443e4a9a401183bbc05f15960", size = 13603, upload-time = "2025-10-21T08:22:48.765Z" }, + { url = "https://files.pythonhosted.org/packages/d7/29/38a4adf7ec6ce28245555ad5cda74a35007fc6c17ab45bf8c31ae4281e22/pyobjc_framework_syncservices-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:ca94dde6e9c9dc068ee20a8130c2a5dd85091ce132b495e92d9f7d5385aef10c", size = 13418, upload-time = "2025-10-21T08:22:50.769Z" }, + { url = "https://files.pythonhosted.org/packages/a3/91/98cd392afe4868ef23debf6bfc2c26220fe20e4783e4d9cc77399a99739b/pyobjc_framework_syncservices-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d63efc5885f347338a57635720caa867888dfe953f607c97fe589b35b1a476f9", size = 13595, upload-time = "2025-10-21T08:22:52.792Z" }, +] + +[[package]] +name = "pyobjc-framework-systemconfiguration" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c1/a5/6d02fec1b04a7b44acf993157fd24ffbd7762c4937f3a733be3ae3899378/pyobjc_framework_systemconfiguration-12.0.tar.gz", hash = "sha256:441738af5663127e0bce23771ddaac25c891c0b09c22254b10a1de0933ed2ca2", size = 59482, upload-time = "2025-10-21T08:41:26.973Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1d/7d/eded231a496a07697f63f7dc3b7eb052a9bcd326b267daaca1ee834dc745/pyobjc_framework_systemconfiguration-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2f0f0a21f74bd771482d7f8e941f9b7f4eec1b8cfb67d88fd043af956e4780d8", size = 21675, upload-time = "2025-10-21T08:22:58.156Z" }, + { url = "https://files.pythonhosted.org/packages/d6/52/0051c6f78624e98ac089312186da04f5350539cfab6c2991aef6da41beda/pyobjc_framework_systemconfiguration-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb08308124703a10bef2257dc0720975bce18fe250cf9c5ee36aaafda4af835b", size = 21589, upload-time = "2025-10-21T08:23:00.828Z" }, + { url = "https://files.pythonhosted.org/packages/d1/99/ca0600867272573786f2efa79cccf7018b442475bd5eed30f8da2cc498f6/pyobjc_framework_systemconfiguration-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e8abae336df40c216ee1bcf9ac5ee40f7fdfdaa3ad96d56d49a7e8c521e27f1c", size = 21582, upload-time = "2025-10-21T08:23:03.682Z" }, + { url = "https://files.pythonhosted.org/packages/59/3d/6bc58890a00a9e853ef9d29c0f9f85b07cafd2d9cb6e116ccdede0d61c60/pyobjc_framework_systemconfiguration-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2583ce2c28b3af11bde74f5317c49ed0ece4fc93391db8a8e5bff77b7c1c524a", size = 22000, upload-time = "2025-10-21T08:23:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/0d/99/e0575334a6470de12ba01bd5fdef875b93760a90766c38d25184fcac0de9/pyobjc_framework_systemconfiguration-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:54b35c020fdc9c6158df217843be3483ad6bc2f7dc99a48a187bdff08bf98074", size = 21620, upload-time = "2025-10-21T08:23:08.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/ab/3036dc52762cc8f18b2171014d57845a904c5b080c8ca4e8043011d84eea/pyobjc_framework_systemconfiguration-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d05f4a4f2a2d7971893b64106f4bbd234366494980cd5db8ce1a49f0ccf69966", size = 22009, upload-time = "2025-10-21T08:23:10.963Z" }, +] + +[[package]] +name = "pyobjc-framework-systemextensions" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/ad/cad5b63d52a11d7e41a378753d30798d47bca41ecd1b519e4c34b1ee1ba7/pyobjc_framework_systemextensions-12.0.tar.gz", hash = "sha256:1eec39afc1a138cc31162577622542e65f0941a001aa4cac0e458bddbad76ba9", size = 21110, upload-time = "2025-10-21T08:41:29.288Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/d0/7424f5475cd7490b7766bc0e5f1310e828c16b16abf84e77315dc565a258/pyobjc_framework_systemextensions-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09f43783346420b8f2f5f692edd847cbd4042ab8a5d639f2195d70e9f04d5db1", size = 9161, upload-time = "2025-10-21T08:23:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/16/1d/b3d16df6bcb5f2521c0eaedbb69fd26b5fc746f65df2a5e3b801b10d9dfd/pyobjc_framework_systemextensions-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:510b0bdfff7da224f96fd50d4c84e64488de13055f525e5572259e77e70dd171", size = 9174, upload-time = "2025-10-21T08:23:16.63Z" }, + { url = "https://files.pythonhosted.org/packages/31/11/bc32194dfd28fcba6baf975582a13bfeac7156c7f10709a0216fa3222dcf/pyobjc_framework_systemextensions-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2c398a5b6c41e65465230acddedb990fac4e558609401f52c15d0a00a00ee0a7", size = 9198, upload-time = "2025-10-21T08:23:18.232Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6b/d1b34b74dc19861a57f947219713bc08ef365c9165fb7ddf47a20deccfad/pyobjc_framework_systemextensions-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:206227d972436cf18300244b5400a3f5b2b6840ca003488b5804b6809430c97e", size = 9354, upload-time = "2025-10-21T08:23:20.197Z" }, + { url = "https://files.pythonhosted.org/packages/06/14/2cc7b2e4c010739cf4ce9ea579c0b935d87fa8d541f726f8fcaab809fd31/pyobjc_framework_systemextensions-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:790bb198dff02fcdeb54f95d5d6d1bec22f5aaa70f6d9bbe46cab4f5c64c0c9e", size = 9265, upload-time = "2025-10-21T08:23:21.794Z" }, + { url = "https://files.pythonhosted.org/packages/fc/10/9c0f1d9d562229df94f380fb929e720e5596efb972a33549158a347dbd50/pyobjc_framework_systemextensions-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:a43dd5f5202b12558bf90382bb10686de9c810b2d5c4bea577e5375c42955687", size = 9423, upload-time = "2025-10-21T08:23:23.372Z" }, +] + +[[package]] +name = "pyobjc-framework-threadnetwork" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/27/7d365ed3228c819e7cb3bf1c00530ad332b16b1f366fa68201ef6802b0e1/pyobjc_framework_threadnetwork-12.0.tar.gz", hash = "sha256:5c4b14ea351f2208e05f3a6b85e46eba4f11ab009af1251ea6caabfb6588dc42", size = 12810, upload-time = "2025-10-21T08:41:31.361Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/63/5e/660f7043d0946d47353f311aa4204e0063ddf768846bac402381542badaa/pyobjc_framework_threadnetwork-12.0-py2.py3-none-any.whl", hash = "sha256:e3f030bd6d36f01480e2f0d0639ada0c21d0d74bcc15f8b6301ebe525180e2f9", size = 3780, upload-time = "2025-10-21T08:23:24.825Z" }, +] + +[[package]] +name = "pyobjc-framework-uniformtypeidentifiers" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/8d/45e8290134b06e73fb1cdce72aea71bddf7d8dee820165a549379d32837e/pyobjc_framework_uniformtypeidentifiers-12.0.tar.gz", hash = "sha256:f7fe17832de25098b9ad7718af536f6f4597985418d9869946cee104e2782b8a", size = 17064, upload-time = "2025-10-21T08:41:33.528Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/04/2b000e6e55572854c20eea7e0f4ba94597a6c8fb22a1fca9f1d2952a1ab6/pyobjc_framework_uniformtypeidentifiers-12.0-py2.py3-none-any.whl", hash = "sha256:b2c406e34306ef55ceb9c8cb16a4a9e37e7fc2ed4c8e7948f05bf3d51dea2a91", size = 4913, upload-time = "2025-10-21T08:23:26.31Z" }, +] + +[[package]] +name = "pyobjc-framework-usernotifications" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e5/fc/3e5d15bddc660fc987cbf72b7b476dbe13bedcf52e18c58606432457d41e/pyobjc_framework_usernotifications-12.0.tar.gz", hash = "sha256:93dea828a26a3a93f6259f21496bcdda5dc1625a48c2ba9ce4a58c8a57d3f84c", size = 30118, upload-time = "2025-10-21T08:41:36.393Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/ad/b59797c1ec7cfc09d77edd1850a5bd8a37df4dfb95bc42b0904dfcab94db/pyobjc_framework_usernotifications-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80a795bea7077e324d0a8d2d210e82ddf2e6cbaaea0c4ad32119fec470c79c24", size = 9640, upload-time = "2025-10-21T08:23:29.719Z" }, + { url = "https://files.pythonhosted.org/packages/8b/68/409a455c1926914e9b973bc167fe3cbae93c7b32189d4de8be0910328aef/pyobjc_framework_usernotifications-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:579cd91b44b3078332e0275e94419cc7b4e5be5b14d774b048ba54d65fc2e60c", size = 9650, upload-time = "2025-10-21T08:23:31.332Z" }, + { url = "https://files.pythonhosted.org/packages/74/b4/4da877831f4fb0c1c87c295792efae21c0c2bc1d8c9f97fb90f261a9e0cf/pyobjc_framework_usernotifications-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fcff4a99268ed3d4d897d061d085188695cd2ad0fe63e16319a7ecbd1af7ddc3", size = 9664, upload-time = "2025-10-21T08:23:33.313Z" }, + { url = "https://files.pythonhosted.org/packages/88/3b/786b4bdbdf67776d625c3bb575f5cbecde868c7ba9840ea1c3bd33670743/pyobjc_framework_usernotifications-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73898e126ee61d429d160e5de5f8f10bf08406e5fbb0a43939d32ebc02f7c165", size = 9819, upload-time = "2025-10-21T08:23:35.238Z" }, + { url = "https://files.pythonhosted.org/packages/f1/58/f6d3cc17d500cb8c4716dad03da5978029483b2794d6d8e06c4d290091bb/pyobjc_framework_usernotifications-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:162802f84c95c63bd0962add355bfcdc56539e7ac3972f002e13f9c4168e7730", size = 9727, upload-time = "2025-10-21T08:23:36.853Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/b0c414798b557dae3c142879fd2c39dbb672e2820ce6ea40ebce83327130/pyobjc_framework_usernotifications-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:6a0da8999950a22643f6fdf294d969a082354bbae2f9e2ee2dfbbf5596c05074", size = 9889, upload-time = "2025-10-21T08:23:38.467Z" }, +] + +[[package]] +name = "pyobjc-framework-usernotificationsui" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-usernotifications" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/07/e7564e9948ad5e834c394cb8b3cfba51312715a91f1cb0e01a9dcf8f5bc5/pyobjc_framework_usernotificationsui-12.0.tar.gz", hash = "sha256:b62eed9660a3b824dd732fca831f111b888af912c8608e0fe7e075de217274b8", size = 13148, upload-time = "2025-10-21T08:41:38.228Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/0f/79602271972bd1060e1ad24973d005be7984f7687278d4b2489021fe0f20/pyobjc_framework_usernotificationsui-12.0-py2.py3-none-any.whl", hash = "sha256:ab0d9fc8e9505daf15e089837125bedf9aec5fa5c49ba0ec91305fab3233977f", size = 3944, upload-time = "2025-10-21T08:23:39.959Z" }, +] + +[[package]] +name = "pyobjc-framework-videosubscriberaccount" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1e/0f/ad63ee1b7b0813dd6505b210f90b9cd39d1e9b5a994c2e2d81e34ce045b0/pyobjc_framework_videosubscriberaccount-12.0.tar.gz", hash = "sha256:45ded32cd5d75323a3c9a692fe0f47fdda3885f16d84c0195908bfe0708db9e3", size = 18836, upload-time = "2025-10-21T08:41:40.268Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/be/ff8942932b0ffe180b7f64fd15fb8503b846040af5a7aceae33a831f0aa3/pyobjc_framework_videosubscriberaccount-12.0-py2.py3-none-any.whl", hash = "sha256:18a495d747252712b65235f98459fec139966060a269eebf55cd56d159640663", size = 4834, upload-time = "2025-10-21T08:23:41.471Z" }, +] + +[[package]] +name = "pyobjc-framework-videotoolbox" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/2f/f85731e4f2ce2c67545dfbe2fbdd1b776b6e2d58e354a4037a2e59803fa0/pyobjc_framework_videotoolbox-12.0.tar.gz", hash = "sha256:69677923fa61fd2ca5acadb404e4be87185cd52946681764986bc43635d27674", size = 58211, upload-time = "2025-10-21T08:41:45.146Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/2e/dfe3c5c7d4b50677d1aa2c6e52ce3757cdfab9a3427f4dca64590b2e80c0/pyobjc_framework_videotoolbox-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:49db730a3020acd1592b91ac224850ae79ce155343135f7f75eddcf1d77be405", size = 18790, upload-time = "2025-10-21T08:23:47.162Z" }, + { url = "https://files.pythonhosted.org/packages/a8/d1/dc2754d6c6d8bf18d21e7a61166b7ba048f794bd6da19565a6b3e0e172bf/pyobjc_framework_videotoolbox-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3589b1698bba7834cde0c55df340ecc74e9c73cc75bea6fced1a5c100df54051", size = 18917, upload-time = "2025-10-21T08:23:49.306Z" }, + { url = "https://files.pythonhosted.org/packages/48/5a/2ea252b95489dcba67c0d22fb60d0969b39cae595f304157ec69da30e976/pyobjc_framework_videotoolbox-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f45d91996796c5d6398205b3e00c6cf651d67e503158ea6e53c9de01901f8ac4", size = 18936, upload-time = "2025-10-21T08:23:51.474Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4a/138107dc891093ab36b4fc0886259286c23af15004ac0f154824d5680d0c/pyobjc_framework_videotoolbox-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17eefbcee1a2e1d74bec281b1995c2dc2017c3c40f1cbaeb69cb6258bbc79feb", size = 19149, upload-time = "2025-10-21T08:23:54.003Z" }, + { url = "https://files.pythonhosted.org/packages/8e/5f/1cb3a83b3de3d0de059b9abbd68f936d53949b42b961a453ec688b361163/pyobjc_framework_videotoolbox-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:f11ec3534dcc02b556232643d53ba62a07fef2de2ff3ff83409290888ed04fa8", size = 18940, upload-time = "2025-10-21T08:23:56.163Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c1/35c68277fbc62daee074fc1ae6f43b27ecd2d840d9a24f43116f854fe3bd/pyobjc_framework_videotoolbox-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:84817ba1912935262852ca7d8687e3e4bd5e5db55fd62c4d54be35b7657ccb2d", size = 19138, upload-time = "2025-10-21T08:23:58.354Z" }, +] + +[[package]] +name = "pyobjc-framework-virtualization" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e6/53/cdba247e9b8252407757edd2e1a7f166b1c8e7a6edf54fc57aa55ca3e0b4/pyobjc_framework_virtualization-12.0.tar.gz", hash = "sha256:0745f57ab3010f10c6e7a424cbfc805f162167687756cce7ef220d1a4fc192cc", size = 41136, upload-time = "2025-10-21T08:41:48.764Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/7e/9f37f76a4d0914911683399f12f947c5380484e7553dd535fdb406fba35c/pyobjc_framework_virtualization-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9f87fd04be9f40cb7f67eeb1783f7fab5b730042e16bc75873cc3c4c608ecb63", size = 13112, upload-time = "2025-10-21T08:24:02.222Z" }, + { url = "https://files.pythonhosted.org/packages/db/e8/722b1f0dc622504f1a7ec7019c2c7e3efad2d0f7a44e9c49fb50a47a9697/pyobjc_framework_virtualization-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27fca13b212d6030571e42a6e2e3199d5a89a432d9db15742061edf170719239", size = 13141, upload-time = "2025-10-21T08:24:04.138Z" }, + { url = "https://files.pythonhosted.org/packages/a3/7b/c5a230ce374334c896bdc6db95586f7a1211d3ff45831175e441a262cb9a/pyobjc_framework_virtualization-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:385baa7b4ff44b1368ab32ad91ec05e667abc687800e3362ad4463d4f81db715", size = 13159, upload-time = "2025-10-21T08:24:06.418Z" }, + { url = "https://files.pythonhosted.org/packages/9e/92/ffff716de121c4077490098b11921580b438b98c05184ab9d54987e16162/pyobjc_framework_virtualization-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7a446087e0806ddf6d09560e80e8b06b79b8039e4abbd6cfca32b9f07736d42e", size = 13365, upload-time = "2025-10-21T08:24:08.338Z" }, + { url = "https://files.pythonhosted.org/packages/41/17/1e1bc10ddd32eb63902b2aebe5f12f32fe82660ae96911ebe9d4a5668b89/pyobjc_framework_virtualization-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:65fbcf184964b52fd60e821c5b2a173fd87d1e4a50afcccfbd3dc909019e1d50", size = 13148, upload-time = "2025-10-21T08:24:10.135Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1f/8c51a1c0149b3a58d0217f516c573114746b701675e48fddab2d3aa29363/pyobjc_framework_virtualization-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:27f27d5aa30dd94c6ad977c11af3d5bc13369950e497007534c1c951c2ca93b5", size = 13352, upload-time = "2025-10-21T08:24:12.335Z" }, +] + +[[package]] +name = "pyobjc-framework-vision" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreml" }, + { name = "pyobjc-framework-quartz" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0f/5a/07cdead5adb77d0742b014fa742d503706754e3ad10e39760e67bb58b497/pyobjc_framework_vision-12.0.tar.gz", hash = "sha256:942c9583f1d887ac9f704f3b0c21b3206b68e02852a87219db4309bb13a02f14", size = 59905, upload-time = "2025-10-21T08:41:53.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/e1/0e865d629a7aba0be220a49b59fa0ac2498c4a10d959288b8544da78d595/pyobjc_framework_vision-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbcba9cbe95116ad96aa05decd189735b213ffd8ee4ec0f81b197c3aaa0af87d", size = 21441, upload-time = "2025-10-21T08:24:17.716Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1b/2043e99b8989b110ddb1eabf6355bd0b412527abda375bafa438f8a255e1/pyobjc_framework_vision-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2d1238127088ef50613a8c022d7b7a8487064d09a83c188e000b90528c8eaf2e", size = 16631, upload-time = "2025-10-21T08:24:20.217Z" }, + { url = "https://files.pythonhosted.org/packages/28/ed/eb94a75b58a9868a32b10cdb59faf0cd877341df80637d1e94beda3fe4e2/pyobjc_framework_vision-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:10c580fcb19a82e19bcc02e782aaaf0cf8ea0d148b95282740e102223127de5a", size = 16646, upload-time = "2025-10-21T08:24:23.039Z" }, + { url = "https://files.pythonhosted.org/packages/62/69/fffcf849bec521d2d8440814c18f6a9865300136489a8c52c1902d10d117/pyobjc_framework_vision-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:12be79c5282a2cf53ac5b69f5edbd15f242d70a21629b728efcf68fc06fbe58b", size = 16790, upload-time = "2025-10-21T08:24:25.134Z" }, + { url = "https://files.pythonhosted.org/packages/36/22/b2962283d4d90efee7ecee0712963810ac02fd08646f6f0ec11fb2e23c47/pyobjc_framework_vision-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:56aae4cb8dd72838c22450c1adc8b5acd2bba9138e116a651e910c4e24293ad9", size = 16623, upload-time = "2025-10-21T08:24:27.463Z" }, + { url = "https://files.pythonhosted.org/packages/94/d2/bc004c6c0a16b2a4eef6a7964ea3f712014c0a94c4ceb9ddaba0c6e2d72c/pyobjc_framework_vision-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:177c996e547a581f7c3ac2502325c1af6db1edbe5f85e9297f5a76df2e33efbf", size = 16780, upload-time = "2025-10-21T08:24:29.75Z" }, +] + +[[package]] +name = "pyobjc-framework-webkit" +version = "12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/6a/9af14df620fd363e58d3676d7182060672f3eace49df78fc36ddbce9b820/pyobjc_framework_webkit-12.0.tar.gz", hash = "sha256:a65a33d7057aed8d096672be4a53a7ea49a7c74a0b4bc9cb216d4773ebfed6d2", size = 284938, upload-time = "2025-10-21T08:42:12.645Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/8e/bf606a62aac481bfc46cbcd1faa540af6bf944cef52725dbc58238e0a361/pyobjc_framework_webkit-12.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:38171cb467ef46ea6a38bcf101bff2f67bc938326fca1a94161e12186ed39a33", size = 49981, upload-time = "2025-10-21T08:24:38.325Z" }, + { url = "https://files.pythonhosted.org/packages/82/75/b8f0451a56584e3a249cbd733bec3f5af449224cb5a1b86550849253f911/pyobjc_framework_webkit-12.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7ac06f5a08b06918498af6fd73a90a368ff9ed104a41d88717a14284db452ead", size = 50087, upload-time = "2025-10-21T08:24:42.556Z" }, + { url = "https://files.pythonhosted.org/packages/19/0b/3897b36ce88ac1201662ffb4373579e9cd477715ca55c197f2cb3c4216ed/pyobjc_framework_webkit-12.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:46fd5e0d8aa3bc57a614dc60eef768abf715cdd873682aadd09df6ee8d31fcda", size = 50104, upload-time = "2025-10-21T08:24:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b9/0d35364f44a0a70b42a536dae503a913a2fef1acd81f9ae4567536b82ac3/pyobjc_framework_webkit-12.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c933ccbdfecdfe3217e32883fa365c3b2cfad601eb25c0a3aee00043aca838fb", size = 50576, upload-time = "2025-10-21T08:24:51.14Z" }, + { url = "https://files.pythonhosted.org/packages/12/e1/dfd6bb0f92e24dec90192c3a10109c99eac8d49f517a1e135d9065daed26/pyobjc_framework_webkit-12.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:bdae2a612d20a4c9038eb7fea2d3a8e1bbb2b21b758d871fb210f8ff1b9d240b", size = 50220, upload-time = "2025-10-21T08:24:55.483Z" }, + { url = "https://files.pythonhosted.org/packages/d9/32/bf22675cd9cde637cb0ec0f7eae8a19d5375cd07448d98e288e9d0798962/pyobjc_framework_webkit-12.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:8db8db7f9225718ec578788b21d56e55560019a158592d17c784f1550612261a", size = 50687, upload-time = "2025-10-21T08:24:59.701Z" }, +] + +[[package]] +name = "pyperclip" +version = "1.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-asyncio" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "python-dotenv" +version = "1.1.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158, upload-time = "2024-12-16T19:45:46.972Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" }, +] + +[[package]] +name = "pytz" +version = "2024.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692, upload-time = "2024-09-11T02:24:47.91Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002, upload-time = "2024-09-11T02:24:45.8Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, + { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, + { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "qdrant-client" +version = "1.15.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "grpcio" }, + { name = "httpx", extra = ["http2"] }, + { name = "numpy", version = "1.26.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, + { name = "numpy", version = "2.3.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, + { name = "portalocker" }, + { name = "protobuf" }, + { name = "pydantic" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/8b/76c7d325e11d97cb8eb5e261c3759e9ed6664735afbf32fdded5b580690c/qdrant_client-1.15.1.tar.gz", hash = "sha256:631f1f3caebfad0fd0c1fba98f41be81d9962b7bf3ca653bed3b727c0e0cbe0e", size = 295297, upload-time = "2025-07-31T19:35:19.627Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/33/d8df6a2b214ffbe4138db9a1efe3248f67dc3c671f82308bea1582ecbbb7/qdrant_client-1.15.1-py3-none-any.whl", hash = "sha256:2b975099b378382f6ca1cfb43f0d59e541be6e16a5892f282a4b8de7eff5cb63", size = 337331, upload-time = "2025-07-31T19:35:17.539Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2025.10.23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/c8/1d2160d36b11fbe0a61acb7c3c81ab032d9ec8ad888ac9e0a61b85ab99dd/regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26", size = 401266, upload-time = "2025-10-21T15:58:20.23Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/e5/74b7cd5cd76b4171f9793042045bb1726f7856dd56e582fc3e058a7a8a5e/regex-2025.10.23-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c531155bf9179345e85032052a1e5fe1a696a6abf9cea54b97e8baefff970fd", size = 487960, upload-time = "2025-10-21T15:54:53.253Z" }, + { url = "https://files.pythonhosted.org/packages/b9/08/854fa4b3b20471d1df1c71e831b6a1aa480281e37791e52a2df9641ec5c6/regex-2025.10.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:912e9df4e89d383681268d38ad8f5780d7cccd94ba0e9aa09ca7ab7ab4f8e7eb", size = 290425, upload-time = "2025-10-21T15:54:55.21Z" }, + { url = "https://files.pythonhosted.org/packages/ab/d3/6272b1dd3ca1271661e168762b234ad3e00dbdf4ef0c7b9b72d2d159efa7/regex-2025.10.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f375c61bfc3138b13e762fe0ae76e3bdca92497816936534a0177201666f44f", size = 288278, upload-time = "2025-10-21T15:54:56.862Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/c7b365dd9d9bc0a36e018cb96f2ffb60d2ba8deb589a712b437f67de2920/regex-2025.10.23-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e248cc9446081119128ed002a3801f8031e0c219b5d3c64d3cc627da29ac0a33", size = 793289, upload-time = "2025-10-21T15:54:58.352Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fb/b8fbe9aa16cf0c21f45ec5a6c74b4cecbf1a1c0deb7089d4a6f83a9c1caa/regex-2025.10.23-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b52bf9282fdf401e4f4e721f0f61fc4b159b1307244517789702407dd74e38ca", size = 860321, upload-time = "2025-10-21T15:54:59.813Z" }, + { url = "https://files.pythonhosted.org/packages/b0/81/bf41405c772324926a9bd8a640dedaa42da0e929241834dfce0733070437/regex-2025.10.23-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c084889ab2c59765a0d5ac602fd1c3c244f9b3fcc9a65fdc7ba6b74c5287490", size = 907011, upload-time = "2025-10-21T15:55:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/a4/fb/5ad6a8b92d3f88f3797b51bb4ef47499acc2d0b53d2fbe4487a892f37a73/regex-2025.10.23-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80e8eb79009bdb0936658c44ca06e2fbbca67792013e3818eea3f5f228971c2", size = 800312, upload-time = "2025-10-21T15:55:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/42/48/b4efba0168a2b57f944205d823f8e8a3a1ae6211a34508f014ec2c712f4f/regex-2025.10.23-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6f259118ba87b814a8ec475380aee5f5ae97a75852a3507cf31d055b01b5b40", size = 782839, upload-time = "2025-10-21T15:55:05.641Z" }, + { url = "https://files.pythonhosted.org/packages/13/2a/c9efb4c6c535b0559c1fa8e431e0574d229707c9ca718600366fcfef6801/regex-2025.10.23-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b8c72a242683dcc72d37595c4f1278dfd7642b769e46700a8df11eab19dfd82", size = 854270, upload-time = "2025-10-21T15:55:07.27Z" }, + { url = "https://files.pythonhosted.org/packages/34/2d/68eecc1bdaee020e8ba549502291c9450d90d8590d0552247c9b543ebf7b/regex-2025.10.23-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d7b7a0a3df9952f9965342159e0c1f05384c0f056a47ce8b61034f8cecbe83", size = 845771, upload-time = "2025-10-21T15:55:09.477Z" }, + { url = "https://files.pythonhosted.org/packages/a5/cd/a1ae499cf9b87afb47a67316bbf1037a7c681ffe447c510ed98c0aa2c01c/regex-2025.10.23-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:413bfea20a484c524858125e92b9ce6ffdd0a4b97d4ff96b5859aa119b0f1bdd", size = 788778, upload-time = "2025-10-21T15:55:11.396Z" }, + { url = "https://files.pythonhosted.org/packages/38/f9/70765e63f5ea7d43b2b6cd4ee9d3323f16267e530fb2a420d92d991cf0fc/regex-2025.10.23-cp311-cp311-win32.whl", hash = "sha256:f76deef1f1019a17dad98f408b8f7afc4bd007cbe835ae77b737e8c7f19ae575", size = 265666, upload-time = "2025-10-21T15:55:13.306Z" }, + { url = "https://files.pythonhosted.org/packages/9c/1a/18e9476ee1b63aaec3844d8e1cb21842dc19272c7e86d879bfc0dcc60db3/regex-2025.10.23-cp311-cp311-win_amd64.whl", hash = "sha256:59bba9f7125536f23fdab5deeea08da0c287a64c1d3acc1c7e99515809824de8", size = 277600, upload-time = "2025-10-21T15:55:15.087Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1b/c019167b1f7a8ec77251457e3ff0339ed74ca8bce1ea13138dc98309c923/regex-2025.10.23-cp311-cp311-win_arm64.whl", hash = "sha256:b103a752b6f1632ca420225718d6ed83f6a6ced3016dd0a4ab9a6825312de566", size = 269974, upload-time = "2025-10-21T15:55:16.841Z" }, + { url = "https://files.pythonhosted.org/packages/f6/57/eeb274d83ab189d02d778851b1ac478477522a92b52edfa6e2ae9ff84679/regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc", size = 489187, upload-time = "2025-10-21T15:55:18.322Z" }, + { url = "https://files.pythonhosted.org/packages/55/5c/7dad43a9b6ea88bf77e0b8b7729a4c36978e1043165034212fd2702880c6/regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2", size = 291122, upload-time = "2025-10-21T15:55:20.2Z" }, + { url = "https://files.pythonhosted.org/packages/66/21/38b71e6f2818f0f4b281c8fba8d9d57cfca7b032a648fa59696e0a54376a/regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45", size = 288797, upload-time = "2025-10-21T15:55:21.932Z" }, + { url = "https://files.pythonhosted.org/packages/be/95/888f069c89e7729732a6d7cca37f76b44bfb53a1e35dda8a2c7b65c1b992/regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e", size = 798442, upload-time = "2025-10-21T15:55:23.747Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/4f903c608faf786627a8ee17c06e0067b5acade473678b69c8094b248705/regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43", size = 864039, upload-time = "2025-10-21T15:55:25.656Z" }, + { url = "https://files.pythonhosted.org/packages/62/19/2df67b526bf25756c7f447dde554fc10a220fd839cc642f50857d01e4a7b/regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca", size = 912057, upload-time = "2025-10-21T15:55:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/99/14/9a39b7c9e007968411bc3c843cc14cf15437510c0a9991f080cab654fd16/regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc", size = 803374, upload-time = "2025-10-21T15:55:28.9Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f7/3495151dd3ca79949599b6d069b72a61a2c5e24fc441dccc79dcaf708fe6/regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a", size = 787714, upload-time = "2025-10-21T15:55:30.628Z" }, + { url = "https://files.pythonhosted.org/packages/28/65/ee882455e051131869957ee8597faea45188c9a98c0dad724cfb302d4580/regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32", size = 858392, upload-time = "2025-10-21T15:55:32.322Z" }, + { url = "https://files.pythonhosted.org/packages/53/25/9287fef5be97529ebd3ac79d256159cb709a07eb58d4be780d1ca3885da8/regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288", size = 850484, upload-time = "2025-10-21T15:55:34.037Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b4/b49b88b4fea2f14dc73e5b5842755e782fc2e52f74423d6f4adc130d5880/regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147", size = 789634, upload-time = "2025-10-21T15:55:35.958Z" }, + { url = "https://files.pythonhosted.org/packages/b6/3c/2f8d199d0e84e78bcd6bdc2be9b62410624f6b796e2893d1837ae738b160/regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079", size = 266060, upload-time = "2025-10-21T15:55:37.902Z" }, + { url = "https://files.pythonhosted.org/packages/d7/67/c35e80969f6ded306ad70b0698863310bdf36aca57ad792f45ddc0e2271f/regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842", size = 276931, upload-time = "2025-10-21T15:55:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/f5/a1/4ed147de7d2b60174f758412c87fa51ada15cd3296a0ff047f4280aaa7ca/regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335", size = 270103, upload-time = "2025-10-21T15:55:41.24Z" }, + { url = "https://files.pythonhosted.org/packages/28/c6/195a6217a43719d5a6a12cc192a22d12c40290cecfa577f00f4fb822f07d/regex-2025.10.23-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7690f95404a1293923a296981fd943cca12c31a41af9c21ba3edd06398fc193", size = 488956, upload-time = "2025-10-21T15:55:42.887Z" }, + { url = "https://files.pythonhosted.org/packages/4c/93/181070cd1aa2fa541ff2d3afcf763ceecd4937b34c615fa92765020a6c90/regex-2025.10.23-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a32d77aeaea58a13230100dd8797ac1a84c457f3af2fdf0d81ea689d5a9105b", size = 290997, upload-time = "2025-10-21T15:55:44.53Z" }, + { url = "https://files.pythonhosted.org/packages/b6/c5/9d37fbe3a40ed8dda78c23e1263002497540c0d1522ed75482ef6c2000f0/regex-2025.10.23-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b24b29402f264f70a3c81f45974323b41764ff7159655360543b7cabb73e7d2f", size = 288686, upload-time = "2025-10-21T15:55:46.186Z" }, + { url = "https://files.pythonhosted.org/packages/5f/e7/db610ff9f10c2921f9b6ac0c8d8be4681b28ddd40fc0549429366967e61f/regex-2025.10.23-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563824a08c7c03d96856d84b46fdb3bbb7cfbdf79da7ef68725cda2ce169c72a", size = 798466, upload-time = "2025-10-21T15:55:48.24Z" }, + { url = "https://files.pythonhosted.org/packages/90/10/aab883e1fa7fe2feb15ac663026e70ca0ae1411efa0c7a4a0342d9545015/regex-2025.10.23-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0ec8bdd88d2e2659c3518087ee34b37e20bd169419ffead4240a7004e8ed03b", size = 863996, upload-time = "2025-10-21T15:55:50.478Z" }, + { url = "https://files.pythonhosted.org/packages/a2/b0/8f686dd97a51f3b37d0238cd00a6d0f9ccabe701f05b56de1918571d0d61/regex-2025.10.23-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b577601bfe1d33913fcd9276d7607bbac827c4798d9e14d04bf37d417a6c41cb", size = 912145, upload-time = "2025-10-21T15:55:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ca/639f8cd5b08797bca38fc5e7e07f76641a428cf8c7fca05894caf045aa32/regex-2025.10.23-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c9f2c68ac6cb3de94eea08a437a75eaa2bd33f9e97c84836ca0b610a5804368", size = 803370, upload-time = "2025-10-21T15:55:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/a40725bb76959eddf8abc42a967bed6f4851b39f5ac4f20e9794d7832aa5/regex-2025.10.23-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89f8b9ea3830c79468e26b0e21c3585f69f105157c2154a36f6b7839f8afb351", size = 787767, upload-time = "2025-10-21T15:55:56.004Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d8/8ee9858062936b0f99656dce390aa667c6e7fb0c357b1b9bf76fb5e2e708/regex-2025.10.23-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:98fd84c4e4ea185b3bb5bf065261ab45867d8875032f358a435647285c722673", size = 858335, upload-time = "2025-10-21T15:55:58.185Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0a/ed5faaa63fa8e3064ab670e08061fbf09e3a10235b19630cf0cbb9e48c0a/regex-2025.10.23-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1e11d3e5887b8b096f96b4154dfb902f29c723a9556639586cd140e77e28b313", size = 850402, upload-time = "2025-10-21T15:56:00.023Z" }, + { url = "https://files.pythonhosted.org/packages/79/14/d05f617342f4b2b4a23561da500ca2beab062bfcc408d60680e77ecaf04d/regex-2025.10.23-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f13450328a6634348d47a88367e06b64c9d84980ef6a748f717b13f8ce64e87", size = 789739, upload-time = "2025-10-21T15:56:01.967Z" }, + { url = "https://files.pythonhosted.org/packages/f9/7b/e8ce8eef42a15f2c3461f8b3e6e924bbc86e9605cb534a393aadc8d3aff8/regex-2025.10.23-cp313-cp313-win32.whl", hash = "sha256:37be9296598a30c6a20236248cb8b2c07ffd54d095b75d3a2a2ee5babdc51df1", size = 266054, upload-time = "2025-10-21T15:56:05.291Z" }, + { url = "https://files.pythonhosted.org/packages/71/2d/55184ed6be6473187868d2f2e6a0708195fc58270e62a22cbf26028f2570/regex-2025.10.23-cp313-cp313-win_amd64.whl", hash = "sha256:ea7a3c283ce0f06fe789365841e9174ba05f8db16e2fd6ae00a02df9572c04c0", size = 276917, upload-time = "2025-10-21T15:56:07.303Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d4/927eced0e2bd45c45839e556f987f8c8f8683268dd3c00ad327deb3b0172/regex-2025.10.23-cp313-cp313-win_arm64.whl", hash = "sha256:d9a4953575f300a7bab71afa4cd4ac061c7697c89590a2902b536783eeb49a4f", size = 270105, upload-time = "2025-10-21T15:56:09.857Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b3/95b310605285573341fc062d1d30b19a54f857530e86c805f942c4ff7941/regex-2025.10.23-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7d6606524fa77b3912c9ef52a42ef63c6cfbfc1077e9dc6296cd5da0da286044", size = 491850, upload-time = "2025-10-21T15:56:11.685Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8f/207c2cec01e34e56db1eff606eef46644a60cf1739ecd474627db90ad90b/regex-2025.10.23-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c037aadf4d64bdc38af7db3dbd34877a057ce6524eefcb2914d6d41c56f968cc", size = 292537, upload-time = "2025-10-21T15:56:13.963Z" }, + { url = "https://files.pythonhosted.org/packages/98/3b/025240af4ada1dc0b5f10d73f3e5122d04ce7f8908ab8881e5d82b9d61b6/regex-2025.10.23-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99018c331fb2529084a0c9b4c713dfa49fafb47c7712422e49467c13a636c656", size = 290904, upload-time = "2025-10-21T15:56:16.016Z" }, + { url = "https://files.pythonhosted.org/packages/81/8e/104ac14e2d3450c43db18ec03e1b96b445a94ae510b60138f00ce2cb7ca1/regex-2025.10.23-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd8aba965604d70306eb90a35528f776e59112a7114a5162824d43b76fa27f58", size = 807311, upload-time = "2025-10-21T15:56:17.818Z" }, + { url = "https://files.pythonhosted.org/packages/19/63/78aef90141b7ce0be8a18e1782f764f6997ad09de0e05251f0d2503a914a/regex-2025.10.23-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:238e67264b4013e74136c49f883734f68656adf8257bfa13b515626b31b20f8e", size = 873241, upload-time = "2025-10-21T15:56:19.941Z" }, + { url = "https://files.pythonhosted.org/packages/b3/a8/80eb1201bb49ae4dba68a1b284b4211ed9daa8e74dc600018a10a90399fb/regex-2025.10.23-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2eb48bd9848d66fd04826382f5e8491ae633de3233a3d64d58ceb4ecfa2113a", size = 914794, upload-time = "2025-10-21T15:56:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d5/1984b6ee93281f360a119a5ca1af6a8ca7d8417861671388bf750becc29b/regex-2025.10.23-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d36591ce06d047d0c0fe2fc5f14bfbd5b4525d08a7b6a279379085e13f0e3d0e", size = 812581, upload-time = "2025-10-21T15:56:24.319Z" }, + { url = "https://files.pythonhosted.org/packages/c4/39/11ebdc6d9927172a64ae237d16763145db6bd45ebb4055c17b88edab72a7/regex-2025.10.23-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5d4ece8628d6e364302006366cea3ee887db397faebacc5dacf8ef19e064cf8", size = 795346, upload-time = "2025-10-21T15:56:26.232Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b4/89a591bcc08b5e436af43315284bd233ba77daf0cf20e098d7af12f006c1/regex-2025.10.23-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:39a7e8083959cb1c4ff74e483eecb5a65d3b3e1d821b256e54baf61782c906c6", size = 868214, upload-time = "2025-10-21T15:56:28.597Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/58ba98409c1dbc8316cdb20dafbc63ed267380a07780cafecaf5012dabc9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:842d449a8fefe546f311656cf8c0d6729b08c09a185f1cad94c756210286d6a8", size = 854540, upload-time = "2025-10-21T15:56:30.875Z" }, + { url = "https://files.pythonhosted.org/packages/9a/f2/4a9e9338d67626e2071b643f828a482712ad15889d7268e11e9a63d6f7e9/regex-2025.10.23-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d614986dc68506be8f00474f4f6960e03e4ca9883f7df47744800e7d7c08a494", size = 799346, upload-time = "2025-10-21T15:56:32.725Z" }, + { url = "https://files.pythonhosted.org/packages/63/be/543d35c46bebf6f7bf2be538cca74d6585f25714700c36f37f01b92df551/regex-2025.10.23-cp313-cp313t-win32.whl", hash = "sha256:a5b7a26b51a9df473ec16a1934d117443a775ceb7b39b78670b2e21893c330c9", size = 268657, upload-time = "2025-10-21T15:56:34.577Z" }, + { url = "https://files.pythonhosted.org/packages/14/9f/4dd6b7b612037158bb2c9bcaa710e6fb3c40ad54af441b9c53b3a137a9f1/regex-2025.10.23-cp313-cp313t-win_amd64.whl", hash = "sha256:ce81c5544a5453f61cb6f548ed358cfb111e3b23f3cd42d250a4077a6be2a7b6", size = 280075, upload-time = "2025-10-21T15:56:36.767Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/5bd0672aa65d38c8da6747c17c8b441bdb53d816c569e3261013af8e83cf/regex-2025.10.23-cp313-cp313t-win_arm64.whl", hash = "sha256:e9bf7f6699f490e4e43c44757aa179dab24d1960999c84ab5c3d5377714ed473", size = 271219, upload-time = "2025-10-21T15:56:39.033Z" }, + { url = "https://files.pythonhosted.org/packages/73/f6/0caf29fec943f201fbc8822879c99d31e59c1d51a983d9843ee5cf398539/regex-2025.10.23-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5b5cb5b6344c4c4c24b2dc87b0bfee78202b07ef7633385df70da7fcf6f7cec6", size = 488960, upload-time = "2025-10-21T15:56:40.849Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7d/ebb7085b8fa31c24ce0355107cea2b92229d9050552a01c5d291c42aecea/regex-2025.10.23-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a6ce7973384c37bdf0f371a843f95a6e6f4e1489e10e0cf57330198df72959c5", size = 290932, upload-time = "2025-10-21T15:56:42.875Z" }, + { url = "https://files.pythonhosted.org/packages/27/41/43906867287cbb5ca4cee671c3cc8081e15deef86a8189c3aad9ac9f6b4d/regex-2025.10.23-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2ee3663f2c334959016b56e3bd0dd187cbc73f948e3a3af14c3caaa0c3035d10", size = 288766, upload-time = "2025-10-21T15:56:44.894Z" }, + { url = "https://files.pythonhosted.org/packages/ab/9e/ea66132776700fc77a39b1056e7a5f1308032fead94507e208dc6716b7cd/regex-2025.10.23-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2003cc82a579107e70d013482acce8ba773293f2db534fb532738395c557ff34", size = 798884, upload-time = "2025-10-21T15:56:47.178Z" }, + { url = "https://files.pythonhosted.org/packages/d5/99/aed1453687ab63819a443930770db972c5c8064421f0d9f5da9ad029f26b/regex-2025.10.23-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:182c452279365a93a9f45874f7f191ec1c51e1f1eb41bf2b16563f1a40c1da3a", size = 864768, upload-time = "2025-10-21T15:56:49.793Z" }, + { url = "https://files.pythonhosted.org/packages/99/5d/732fe747a1304805eb3853ce6337eea16b169f7105a0d0dd9c6a5ffa9948/regex-2025.10.23-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b1249e9ff581c5b658c8f0437f883b01f1edcf424a16388591e7c05e5e9e8b0c", size = 911394, upload-time = "2025-10-21T15:56:52.186Z" }, + { url = "https://files.pythonhosted.org/packages/5e/48/58a1f6623466522352a6efa153b9a3714fc559d9f930e9bc947b4a88a2c3/regex-2025.10.23-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b841698f93db3ccc36caa1900d2a3be281d9539b822dc012f08fc80b46a3224", size = 803145, upload-time = "2025-10-21T15:56:55.142Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f6/7dea79be2681a5574ab3fc237aa53b2c1dfd6bd2b44d4640b6c76f33f4c1/regex-2025.10.23-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:956d89e0c92d471e8f7eee73f73fdff5ed345886378c45a43175a77538a1ffe4", size = 787831, upload-time = "2025-10-21T15:56:57.203Z" }, + { url = "https://files.pythonhosted.org/packages/3a/ad/07b76950fbbe65f88120ca2d8d845047c401450f607c99ed38862904671d/regex-2025.10.23-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5c259cb363299a0d90d63b5c0d7568ee98419861618a95ee9d91a41cb9954462", size = 859162, upload-time = "2025-10-21T15:56:59.195Z" }, + { url = "https://files.pythonhosted.org/packages/41/87/374f3b2021b22aa6a4fc0b750d63f9721e53d1631a238f7a1c343c1cd288/regex-2025.10.23-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:185d2b18c062820b3a40d8fefa223a83f10b20a674bf6e8c4a432e8dfd844627", size = 849899, upload-time = "2025-10-21T15:57:01.747Z" }, + { url = "https://files.pythonhosted.org/packages/12/4a/7f7bb17c5a5a9747249807210e348450dab9212a46ae6d23ebce86ba6a2b/regex-2025.10.23-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:281d87fa790049c2b7c1b4253121edd80b392b19b5a3d28dc2a77579cb2a58ec", size = 789372, upload-time = "2025-10-21T15:57:04.018Z" }, + { url = "https://files.pythonhosted.org/packages/c9/dd/9c7728ff544fea09bbc8635e4c9e7c423b11c24f1a7a14e6ac4831466709/regex-2025.10.23-cp314-cp314-win32.whl", hash = "sha256:63b81eef3656072e4ca87c58084c7a9c2b81d41a300b157be635a8a675aacfb8", size = 271451, upload-time = "2025-10-21T15:57:06.266Z" }, + { url = "https://files.pythonhosted.org/packages/48/f8/ef7837ff858eb74079c4804c10b0403c0b740762e6eedba41062225f7117/regex-2025.10.23-cp314-cp314-win_amd64.whl", hash = "sha256:0967c5b86f274800a34a4ed862dfab56928144d03cb18821c5153f8777947796", size = 280173, upload-time = "2025-10-21T15:57:08.206Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d0/d576e1dbd9885bfcd83d0e90762beea48d9373a6f7ed39170f44ed22e336/regex-2025.10.23-cp314-cp314-win_arm64.whl", hash = "sha256:c70dfe58b0a00b36aa04cdb0f798bf3e0adc31747641f69e191109fd8572c9a9", size = 273206, upload-time = "2025-10-21T15:57:10.367Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d0/2025268315e8b2b7b660039824cb7765a41623e97d4cd421510925400487/regex-2025.10.23-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1f5799ea1787aa6de6c150377d11afad39a38afd033f0c5247aecb997978c422", size = 491854, upload-time = "2025-10-21T15:57:12.526Z" }, + { url = "https://files.pythonhosted.org/packages/44/35/5681c2fec5e8b33454390af209c4353dfc44606bf06d714b0b8bd0454ffe/regex-2025.10.23-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a9639ab7540cfea45ef57d16dcbea2e22de351998d614c3ad2f9778fa3bdd788", size = 292542, upload-time = "2025-10-21T15:57:15.158Z" }, + { url = "https://files.pythonhosted.org/packages/5d/17/184eed05543b724132e4a18149e900f5189001fcfe2d64edaae4fbaf36b4/regex-2025.10.23-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:08f52122c352eb44c3421dab78b9b73a8a77a282cc8314ae576fcaa92b780d10", size = 290903, upload-time = "2025-10-21T15:57:17.108Z" }, + { url = "https://files.pythonhosted.org/packages/25/d0/5e3347aa0db0de382dddfa133a7b0ae72f24b4344f3989398980b44a3924/regex-2025.10.23-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebf1baebef1c4088ad5a5623decec6b52950f0e4d7a0ae4d48f0a99f8c9cb7d7", size = 807546, upload-time = "2025-10-21T15:57:19.179Z" }, + { url = "https://files.pythonhosted.org/packages/d2/bb/40c589bbdce1be0c55e9f8159789d58d47a22014f2f820cf2b517a5cd193/regex-2025.10.23-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:16b0f1c2e2d566c562d5c384c2b492646be0a19798532fdc1fdedacc66e3223f", size = 873322, upload-time = "2025-10-21T15:57:21.36Z" }, + { url = "https://files.pythonhosted.org/packages/fe/56/a7e40c01575ac93360e606278d359f91829781a9f7fb6e5aa435039edbda/regex-2025.10.23-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7ada5d9dceafaab92646aa00c10a9efd9b09942dd9b0d7c5a4b73db92cc7e61", size = 914855, upload-time = "2025-10-21T15:57:24.044Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4b/d55587b192763db3163c3f508b3b67b31bb6f5e7a0e08b83013d0a59500a/regex-2025.10.23-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a36b4005770044bf08edecc798f0e41a75795b9e7c9c12fe29da8d792ef870c", size = 812724, upload-time = "2025-10-21T15:57:26.123Z" }, + { url = "https://files.pythonhosted.org/packages/33/20/18bac334955fbe99d17229f4f8e98d05e4a501ac03a442be8facbb37c304/regex-2025.10.23-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:af7b2661dcc032da1fae82069b5ebf2ac1dfcd5359ef8b35e1367bfc92181432", size = 795439, upload-time = "2025-10-21T15:57:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/67/46/c57266be9df8549c7d85deb4cb82280cb0019e46fff677534c5fa1badfa4/regex-2025.10.23-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb976810ac1416a67562c2e5ba0accf6f928932320fef302e08100ed681b38e", size = 868336, upload-time = "2025-10-21T15:57:30.867Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f3/bd5879e41ef8187fec5e678e94b526a93f99e7bbe0437b0f2b47f9101694/regex-2025.10.23-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1a56a54be3897d62f54290190fbcd754bff6932934529fbf5b29933da28fcd43", size = 854567, upload-time = "2025-10-21T15:57:33.062Z" }, + { url = "https://files.pythonhosted.org/packages/e6/57/2b6bbdbd2f24dfed5b028033aa17ad8f7d86bb28f1a892cac8b3bc89d059/regex-2025.10.23-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8f3e6d202fb52c2153f532043bbcf618fd177df47b0b306741eb9b60ba96edc3", size = 799565, upload-time = "2025-10-21T15:57:35.153Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ba/a6168f542ba73b151ed81237adf6b869c7b2f7f8d51618111296674e20ee/regex-2025.10.23-cp314-cp314t-win32.whl", hash = "sha256:1fa1186966b2621b1769fd467c7b22e317e6ba2d2cdcecc42ea3089ef04a8521", size = 274428, upload-time = "2025-10-21T15:57:37.996Z" }, + { url = "https://files.pythonhosted.org/packages/ef/a0/c84475e14a2829e9b0864ebf77c3f7da909df9d8acfe2bb540ff0072047c/regex-2025.10.23-cp314-cp314t-win_amd64.whl", hash = "sha256:08a15d40ce28362eac3e78e83d75475147869c1ff86bc93285f43b4f4431a741", size = 284140, upload-time = "2025-10-21T15:57:40.027Z" }, + { url = "https://files.pythonhosted.org/packages/51/33/6a08ade0eee5b8ba79386869fa6f77afeb835b60510f3525db987e2fffc4/regex-2025.10.23-cp314-cp314t-win_arm64.whl", hash = "sha256:a93e97338e1c8ea2649e130dcfbe8cd69bba5e1e163834752ab64dcb4de6d5ed", size = 274497, upload-time = "2025-10-21T15:57:42.389Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rich" +version = "14.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.27.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/dd/2c0cbe774744272b0ae725f44032c77bdcab6e8bcf544bffa3b6e70c8dba/rpds_py-0.27.1.tar.gz", hash = "sha256:26a1c73171d10b7acccbded82bf6a586ab8203601e565badc74bbbf8bc5a10f8", size = 27479, upload-time = "2025-08-27T12:16:36.024Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b5/c1/7907329fbef97cbd49db6f7303893bd1dd5a4a3eae415839ffdfb0762cae/rpds_py-0.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:be898f271f851f68b318872ce6ebebbc62f303b654e43bf72683dbdc25b7c881", size = 371063, upload-time = "2025-08-27T12:12:47.856Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/2aab4bc86228bcf7c48760990273653a4900de89c7537ffe1b0d6097ed39/rpds_py-0.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:62ac3d4e3e07b58ee0ddecd71d6ce3b1637de2d373501412df395a0ec5f9beb5", size = 353210, upload-time = "2025-08-27T12:12:49.187Z" }, + { url = "https://files.pythonhosted.org/packages/3a/57/f5eb3ecf434342f4f1a46009530e93fd201a0b5b83379034ebdb1d7c1a58/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4708c5c0ceb2d034f9991623631d3d23cb16e65c83736ea020cdbe28d57c0a0e", size = 381636, upload-time = "2025-08-27T12:12:50.492Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f4/ef95c5945e2ceb5119571b184dd5a1cc4b8541bbdf67461998cfeac9cb1e/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:abfa1171a9952d2e0002aba2ad3780820b00cc3d9c98c6630f2e93271501f66c", size = 394341, upload-time = "2025-08-27T12:12:52.024Z" }, + { url = "https://files.pythonhosted.org/packages/5a/7e/4bd610754bf492d398b61725eb9598ddd5eb86b07d7d9483dbcd810e20bc/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b507d19f817ebaca79574b16eb2ae412e5c0835542c93fe9983f1e432aca195", size = 523428, upload-time = "2025-08-27T12:12:53.779Z" }, + { url = "https://files.pythonhosted.org/packages/9f/e5/059b9f65a8c9149361a8b75094864ab83b94718344db511fd6117936ed2a/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:168b025f8fd8d8d10957405f3fdcef3dc20f5982d398f90851f4abc58c566c52", size = 402923, upload-time = "2025-08-27T12:12:55.15Z" }, + { url = "https://files.pythonhosted.org/packages/f5/48/64cabb7daced2968dd08e8a1b7988bf358d7bd5bcd5dc89a652f4668543c/rpds_py-0.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb56c6210ef77caa58e16e8c17d35c63fe3f5b60fd9ba9d424470c3400bcf9ed", size = 384094, upload-time = "2025-08-27T12:12:57.194Z" }, + { url = "https://files.pythonhosted.org/packages/ae/e1/dc9094d6ff566bff87add8a510c89b9e158ad2ecd97ee26e677da29a9e1b/rpds_py-0.27.1-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:d252f2d8ca0195faa707f8eb9368955760880b2b42a8ee16d382bf5dd807f89a", size = 401093, upload-time = "2025-08-27T12:12:58.985Z" }, + { url = "https://files.pythonhosted.org/packages/37/8e/ac8577e3ecdd5593e283d46907d7011618994e1d7ab992711ae0f78b9937/rpds_py-0.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6e5e54da1e74b91dbc7996b56640f79b195d5925c2b78efaa8c5d53e1d88edde", size = 417969, upload-time = "2025-08-27T12:13:00.367Z" }, + { url = "https://files.pythonhosted.org/packages/66/6d/87507430a8f74a93556fe55c6485ba9c259949a853ce407b1e23fea5ba31/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ffce0481cc6e95e5b3f0a47ee17ffbd234399e6d532f394c8dce320c3b089c21", size = 558302, upload-time = "2025-08-27T12:13:01.737Z" }, + { url = "https://files.pythonhosted.org/packages/3a/bb/1db4781ce1dda3eecc735e3152659a27b90a02ca62bfeea17aee45cc0fbc/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a205fdfe55c90c2cd8e540ca9ceba65cbe6629b443bc05db1f590a3db8189ff9", size = 589259, upload-time = "2025-08-27T12:13:03.127Z" }, + { url = "https://files.pythonhosted.org/packages/7b/0e/ae1c8943d11a814d01b482e1f8da903f88047a962dff9bbdadf3bd6e6fd1/rpds_py-0.27.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:689fb5200a749db0415b092972e8eba85847c23885c8543a8b0f5c009b1a5948", size = 554983, upload-time = "2025-08-27T12:13:04.516Z" }, + { url = "https://files.pythonhosted.org/packages/b2/d5/0b2a55415931db4f112bdab072443ff76131b5ac4f4dc98d10d2d357eb03/rpds_py-0.27.1-cp311-cp311-win32.whl", hash = "sha256:3182af66048c00a075010bc7f4860f33913528a4b6fc09094a6e7598e462fe39", size = 217154, upload-time = "2025-08-27T12:13:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/24/75/3b7ffe0d50dc86a6a964af0d1cc3a4a2cdf437cb7b099a4747bbb96d1819/rpds_py-0.27.1-cp311-cp311-win_amd64.whl", hash = "sha256:b4938466c6b257b2f5c4ff98acd8128ec36b5059e5c8f8372d79316b1c36bb15", size = 228627, upload-time = "2025-08-27T12:13:07.625Z" }, + { url = "https://files.pythonhosted.org/packages/8d/3f/4fd04c32abc02c710f09a72a30c9a55ea3cc154ef8099078fd50a0596f8e/rpds_py-0.27.1-cp311-cp311-win_arm64.whl", hash = "sha256:2f57af9b4d0793e53266ee4325535a31ba48e2f875da81a9177c9926dfa60746", size = 220998, upload-time = "2025-08-27T12:13:08.972Z" }, + { url = "https://files.pythonhosted.org/packages/bd/fe/38de28dee5df58b8198c743fe2bea0c785c6d40941b9950bac4cdb71a014/rpds_py-0.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ae2775c1973e3c30316892737b91f9283f9908e3cc7625b9331271eaaed7dc90", size = 361887, upload-time = "2025-08-27T12:13:10.233Z" }, + { url = "https://files.pythonhosted.org/packages/7c/9a/4b6c7eedc7dd90986bf0fab6ea2a091ec11c01b15f8ba0a14d3f80450468/rpds_py-0.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2643400120f55c8a96f7c9d858f7be0c88d383cd4653ae2cf0d0c88f668073e5", size = 345795, upload-time = "2025-08-27T12:13:11.65Z" }, + { url = "https://files.pythonhosted.org/packages/6f/0e/e650e1b81922847a09cca820237b0edee69416a01268b7754d506ade11ad/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16323f674c089b0360674a4abd28d5042947d54ba620f72514d69be4ff64845e", size = 385121, upload-time = "2025-08-27T12:13:13.008Z" }, + { url = "https://files.pythonhosted.org/packages/1b/ea/b306067a712988e2bff00dcc7c8f31d26c29b6d5931b461aa4b60a013e33/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9a1f4814b65eacac94a00fc9a526e3fdafd78e439469644032032d0d63de4881", size = 398976, upload-time = "2025-08-27T12:13:14.368Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0a/26dc43c8840cb8fe239fe12dbc8d8de40f2365e838f3d395835dde72f0e5/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba32c16b064267b22f1850a34051121d423b6f7338a12b9459550eb2096e7ec", size = 525953, upload-time = "2025-08-27T12:13:15.774Z" }, + { url = "https://files.pythonhosted.org/packages/22/14/c85e8127b573aaf3a0cbd7fbb8c9c99e735a4a02180c84da2a463b766e9e/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5c20f33fd10485b80f65e800bbe5f6785af510b9f4056c5a3c612ebc83ba6cb", size = 407915, upload-time = "2025-08-27T12:13:17.379Z" }, + { url = "https://files.pythonhosted.org/packages/ed/7b/8f4fee9ba1fb5ec856eb22d725a4efa3deb47f769597c809e03578b0f9d9/rpds_py-0.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:466bfe65bd932da36ff279ddd92de56b042f2266d752719beb97b08526268ec5", size = 386883, upload-time = "2025-08-27T12:13:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/86/47/28fa6d60f8b74fcdceba81b272f8d9836ac0340570f68f5df6b41838547b/rpds_py-0.27.1-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:41e532bbdcb57c92ba3be62c42e9f096431b4cf478da9bc3bc6ce5c38ab7ba7a", size = 405699, upload-time = "2025-08-27T12:13:20.089Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fd/c5987b5e054548df56953a21fe2ebed51fc1ec7c8f24fd41c067b68c4a0a/rpds_py-0.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f149826d742b406579466283769a8ea448eed82a789af0ed17b0cd5770433444", size = 423713, upload-time = "2025-08-27T12:13:21.436Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ba/3c4978b54a73ed19a7d74531be37a8bcc542d917c770e14d372b8daea186/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:80c60cfb5310677bd67cb1e85a1e8eb52e12529545441b43e6f14d90b878775a", size = 562324, upload-time = "2025-08-27T12:13:22.789Z" }, + { url = "https://files.pythonhosted.org/packages/b5/6c/6943a91768fec16db09a42b08644b960cff540c66aab89b74be6d4a144ba/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7ee6521b9baf06085f62ba9c7a3e5becffbc32480d2f1b351559c001c38ce4c1", size = 593646, upload-time = "2025-08-27T12:13:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/9d7a8f4be5f4396f011a6bb7a19fe26303a0dac9064462f5651ced2f572f/rpds_py-0.27.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a512c8263249a9d68cac08b05dd59d2b3f2061d99b322813cbcc14c3c7421998", size = 558137, upload-time = "2025-08-27T12:13:25.557Z" }, + { url = "https://files.pythonhosted.org/packages/6e/96/6772cbfa0e2485bcceef8071de7821f81aeac8bb45fbfd5542a3e8108165/rpds_py-0.27.1-cp312-cp312-win32.whl", hash = "sha256:819064fa048ba01b6dadc5116f3ac48610435ac9a0058bbde98e569f9e785c39", size = 221343, upload-time = "2025-08-27T12:13:26.967Z" }, + { url = "https://files.pythonhosted.org/packages/67/b6/c82f0faa9af1c6a64669f73a17ee0eeef25aff30bb9a1c318509efe45d84/rpds_py-0.27.1-cp312-cp312-win_amd64.whl", hash = "sha256:d9199717881f13c32c4046a15f024971a3b78ad4ea029e8da6b86e5aa9cf4594", size = 232497, upload-time = "2025-08-27T12:13:28.326Z" }, + { url = "https://files.pythonhosted.org/packages/e1/96/2817b44bd2ed11aebacc9251da03689d56109b9aba5e311297b6902136e2/rpds_py-0.27.1-cp312-cp312-win_arm64.whl", hash = "sha256:33aa65b97826a0e885ef6e278fbd934e98cdcfed80b63946025f01e2f5b29502", size = 222790, upload-time = "2025-08-27T12:13:29.71Z" }, + { url = "https://files.pythonhosted.org/packages/cc/77/610aeee8d41e39080c7e14afa5387138e3c9fa9756ab893d09d99e7d8e98/rpds_py-0.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e4b9fcfbc021633863a37e92571d6f91851fa656f0180246e84cbd8b3f6b329b", size = 361741, upload-time = "2025-08-27T12:13:31.039Z" }, + { url = "https://files.pythonhosted.org/packages/3a/fc/c43765f201c6a1c60be2043cbdb664013def52460a4c7adace89d6682bf4/rpds_py-0.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1441811a96eadca93c517d08df75de45e5ffe68aa3089924f963c782c4b898cf", size = 345574, upload-time = "2025-08-27T12:13:32.902Z" }, + { url = "https://files.pythonhosted.org/packages/20/42/ee2b2ca114294cd9847d0ef9c26d2b0851b2e7e00bf14cc4c0b581df0fc3/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55266dafa22e672f5a4f65019015f90336ed31c6383bd53f5e7826d21a0e0b83", size = 385051, upload-time = "2025-08-27T12:13:34.228Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e8/1e430fe311e4799e02e2d1af7c765f024e95e17d651612425b226705f910/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d78827d7ac08627ea2c8e02c9e5b41180ea5ea1f747e9db0915e3adf36b62dcf", size = 398395, upload-time = "2025-08-27T12:13:36.132Z" }, + { url = "https://files.pythonhosted.org/packages/82/95/9dc227d441ff2670651c27a739acb2535ccaf8b351a88d78c088965e5996/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae92443798a40a92dc5f0b01d8a7c93adde0c4dc965310a29ae7c64d72b9fad2", size = 524334, upload-time = "2025-08-27T12:13:37.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/01/a670c232f401d9ad461d9a332aa4080cd3cb1d1df18213dbd0d2a6a7ab51/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c46c9dd2403b66a2a3b9720ec4b74d4ab49d4fabf9f03dfdce2d42af913fe8d0", size = 407691, upload-time = "2025-08-27T12:13:38.94Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/0a14aebbaa26fe7fab4780c76f2239e76cc95a0090bdb25e31d95c492fcd/rpds_py-0.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2efe4eb1d01b7f5f1939f4ef30ecea6c6b3521eec451fb93191bf84b2a522418", size = 386868, upload-time = "2025-08-27T12:13:40.192Z" }, + { url = "https://files.pythonhosted.org/packages/3b/03/8c897fb8b5347ff6c1cc31239b9611c5bf79d78c984430887a353e1409a1/rpds_py-0.27.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:15d3b4d83582d10c601f481eca29c3f138d44c92187d197aff663a269197c02d", size = 405469, upload-time = "2025-08-27T12:13:41.496Z" }, + { url = "https://files.pythonhosted.org/packages/da/07/88c60edc2df74850d496d78a1fdcdc7b54360a7f610a4d50008309d41b94/rpds_py-0.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4ed2e16abbc982a169d30d1a420274a709949e2cbdef119fe2ec9d870b42f274", size = 422125, upload-time = "2025-08-27T12:13:42.802Z" }, + { url = "https://files.pythonhosted.org/packages/6b/86/5f4c707603e41b05f191a749984f390dabcbc467cf833769b47bf14ba04f/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a75f305c9b013289121ec0f1181931975df78738cdf650093e6b86d74aa7d8dd", size = 562341, upload-time = "2025-08-27T12:13:44.472Z" }, + { url = "https://files.pythonhosted.org/packages/b2/92/3c0cb2492094e3cd9baf9e49bbb7befeceb584ea0c1a8b5939dca4da12e5/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:67ce7620704745881a3d4b0ada80ab4d99df390838839921f99e63c474f82cf2", size = 592511, upload-time = "2025-08-27T12:13:45.898Z" }, + { url = "https://files.pythonhosted.org/packages/10/bb/82e64fbb0047c46a168faa28d0d45a7851cd0582f850b966811d30f67ad8/rpds_py-0.27.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d992ac10eb86d9b6f369647b6a3f412fc0075cfd5d799530e84d335e440a002", size = 557736, upload-time = "2025-08-27T12:13:47.408Z" }, + { url = "https://files.pythonhosted.org/packages/00/95/3c863973d409210da7fb41958172c6b7dbe7fc34e04d3cc1f10bb85e979f/rpds_py-0.27.1-cp313-cp313-win32.whl", hash = "sha256:4f75e4bd8ab8db624e02c8e2fc4063021b58becdbe6df793a8111d9343aec1e3", size = 221462, upload-time = "2025-08-27T12:13:48.742Z" }, + { url = "https://files.pythonhosted.org/packages/ce/2c/5867b14a81dc217b56d95a9f2a40fdbc56a1ab0181b80132beeecbd4b2d6/rpds_py-0.27.1-cp313-cp313-win_amd64.whl", hash = "sha256:f9025faafc62ed0b75a53e541895ca272815bec18abe2249ff6501c8f2e12b83", size = 232034, upload-time = "2025-08-27T12:13:50.11Z" }, + { url = "https://files.pythonhosted.org/packages/c7/78/3958f3f018c01923823f1e47f1cc338e398814b92d83cd278364446fac66/rpds_py-0.27.1-cp313-cp313-win_arm64.whl", hash = "sha256:ed10dc32829e7d222b7d3b93136d25a406ba9788f6a7ebf6809092da1f4d279d", size = 222392, upload-time = "2025-08-27T12:13:52.587Z" }, + { url = "https://files.pythonhosted.org/packages/01/76/1cdf1f91aed5c3a7bf2eba1f1c4e4d6f57832d73003919a20118870ea659/rpds_py-0.27.1-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:92022bbbad0d4426e616815b16bc4127f83c9a74940e1ccf3cfe0b387aba0228", size = 358355, upload-time = "2025-08-27T12:13:54.012Z" }, + { url = "https://files.pythonhosted.org/packages/c3/6f/bf142541229374287604caf3bb2a4ae17f0a580798fd72d3b009b532db4e/rpds_py-0.27.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47162fdab9407ec3f160805ac3e154df042e577dd53341745fc7fb3f625e6d92", size = 342138, upload-time = "2025-08-27T12:13:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/1a/77/355b1c041d6be40886c44ff5e798b4e2769e497b790f0f7fd1e78d17e9a8/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb89bec23fddc489e5d78b550a7b773557c9ab58b7946154a10a6f7a214a48b2", size = 380247, upload-time = "2025-08-27T12:13:57.683Z" }, + { url = "https://files.pythonhosted.org/packages/d6/a4/d9cef5c3946ea271ce2243c51481971cd6e34f21925af2783dd17b26e815/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e48af21883ded2b3e9eb48cb7880ad8598b31ab752ff3be6457001d78f416723", size = 390699, upload-time = "2025-08-27T12:13:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/3a/06/005106a7b8c6c1a7e91b73169e49870f4af5256119d34a361ae5240a0c1d/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f5b7bd8e219ed50299e58551a410b64daafb5017d54bbe822e003856f06a802", size = 521852, upload-time = "2025-08-27T12:14:00.583Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3e/50fb1dac0948e17a02eb05c24510a8fe12d5ce8561c6b7b7d1339ab7ab9c/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08f1e20bccf73b08d12d804d6e1c22ca5530e71659e6673bce31a6bb71c1e73f", size = 402582, upload-time = "2025-08-27T12:14:02.034Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b0/f4e224090dc5b0ec15f31a02d746ab24101dd430847c4d99123798661bfc/rpds_py-0.27.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0dc5dceeaefcc96dc192e3a80bbe1d6c410c469e97bdd47494a7d930987f18b2", size = 384126, upload-time = "2025-08-27T12:14:03.437Z" }, + { url = "https://files.pythonhosted.org/packages/54/77/ac339d5f82b6afff1df8f0fe0d2145cc827992cb5f8eeb90fc9f31ef7a63/rpds_py-0.27.1-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d76f9cc8665acdc0c9177043746775aa7babbf479b5520b78ae4002d889f5c21", size = 399486, upload-time = "2025-08-27T12:14:05.443Z" }, + { url = "https://files.pythonhosted.org/packages/d6/29/3e1c255eee6ac358c056a57d6d6869baa00a62fa32eea5ee0632039c50a3/rpds_py-0.27.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:134fae0e36022edad8290a6661edf40c023562964efea0cc0ec7f5d392d2aaef", size = 414832, upload-time = "2025-08-27T12:14:06.902Z" }, + { url = "https://files.pythonhosted.org/packages/3f/db/6d498b844342deb3fa1d030598db93937a9964fcf5cb4da4feb5f17be34b/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb11a4f1b2b63337cfd3b4d110af778a59aae51c81d195768e353d8b52f88081", size = 557249, upload-time = "2025-08-27T12:14:08.37Z" }, + { url = "https://files.pythonhosted.org/packages/60/f3/690dd38e2310b6f68858a331399b4d6dbb9132c3e8ef8b4333b96caf403d/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:13e608ac9f50a0ed4faec0e90ece76ae33b34c0e8656e3dceb9a7db994c692cd", size = 587356, upload-time = "2025-08-27T12:14:10.034Z" }, + { url = "https://files.pythonhosted.org/packages/86/e3/84507781cccd0145f35b1dc32c72675200c5ce8d5b30f813e49424ef68fc/rpds_py-0.27.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dd2135527aa40f061350c3f8f89da2644de26cd73e4de458e79606384f4f68e7", size = 555300, upload-time = "2025-08-27T12:14:11.783Z" }, + { url = "https://files.pythonhosted.org/packages/e5/ee/375469849e6b429b3516206b4580a79e9ef3eb12920ddbd4492b56eaacbe/rpds_py-0.27.1-cp313-cp313t-win32.whl", hash = "sha256:3020724ade63fe320a972e2ffd93b5623227e684315adce194941167fee02688", size = 216714, upload-time = "2025-08-27T12:14:13.629Z" }, + { url = "https://files.pythonhosted.org/packages/21/87/3fc94e47c9bd0742660e84706c311a860dcae4374cf4a03c477e23ce605a/rpds_py-0.27.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8ee50c3e41739886606388ba3ab3ee2aae9f35fb23f833091833255a31740797", size = 228943, upload-time = "2025-08-27T12:14:14.937Z" }, + { url = "https://files.pythonhosted.org/packages/70/36/b6e6066520a07cf029d385de869729a895917b411e777ab1cde878100a1d/rpds_py-0.27.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:acb9aafccaae278f449d9c713b64a9e68662e7799dbd5859e2c6b3c67b56d334", size = 362472, upload-time = "2025-08-27T12:14:16.333Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/b4646032e0dcec0df9c73a3bd52f63bc6c5f9cda992f06bd0e73fe3fbebd/rpds_py-0.27.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:b7fb801aa7f845ddf601c49630deeeccde7ce10065561d92729bfe81bd21fb33", size = 345676, upload-time = "2025-08-27T12:14:17.764Z" }, + { url = "https://files.pythonhosted.org/packages/b0/16/2f1003ee5d0af4bcb13c0cf894957984c32a6751ed7206db2aee7379a55e/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0dd05afb46597b9a2e11c351e5e4283c741237e7f617ffb3252780cca9336a", size = 385313, upload-time = "2025-08-27T12:14:19.829Z" }, + { url = "https://files.pythonhosted.org/packages/05/cd/7eb6dd7b232e7f2654d03fa07f1414d7dfc980e82ba71e40a7c46fd95484/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b6dfb0e058adb12d8b1d1b25f686e94ffa65d9995a5157afe99743bf7369d62b", size = 399080, upload-time = "2025-08-27T12:14:21.531Z" }, + { url = "https://files.pythonhosted.org/packages/20/51/5829afd5000ec1cb60f304711f02572d619040aa3ec033d8226817d1e571/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed090ccd235f6fa8bb5861684567f0a83e04f52dfc2e5c05f2e4b1309fcf85e7", size = 523868, upload-time = "2025-08-27T12:14:23.485Z" }, + { url = "https://files.pythonhosted.org/packages/05/2c/30eebca20d5db95720ab4d2faec1b5e4c1025c473f703738c371241476a2/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf876e79763eecf3e7356f157540d6a093cef395b65514f17a356f62af6cc136", size = 408750, upload-time = "2025-08-27T12:14:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/90/1a/cdb5083f043597c4d4276eae4e4c70c55ab5accec078da8611f24575a367/rpds_py-0.27.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12ed005216a51b1d6e2b02a7bd31885fe317e45897de81d86dcce7d74618ffff", size = 387688, upload-time = "2025-08-27T12:14:27.537Z" }, + { url = "https://files.pythonhosted.org/packages/7c/92/cf786a15320e173f945d205ab31585cc43969743bb1a48b6888f7a2b0a2d/rpds_py-0.27.1-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:ee4308f409a40e50593c7e3bb8cbe0b4d4c66d1674a316324f0c2f5383b486f9", size = 407225, upload-time = "2025-08-27T12:14:28.981Z" }, + { url = "https://files.pythonhosted.org/packages/33/5c/85ee16df5b65063ef26017bef33096557a4c83fbe56218ac7cd8c235f16d/rpds_py-0.27.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0b08d152555acf1f455154d498ca855618c1378ec810646fcd7c76416ac6dc60", size = 423361, upload-time = "2025-08-27T12:14:30.469Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8e/1c2741307fcabd1a334ecf008e92c4f47bb6f848712cf15c923becfe82bb/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dce51c828941973a5684d458214d3a36fcd28da3e1875d659388f4f9f12cc33e", size = 562493, upload-time = "2025-08-27T12:14:31.987Z" }, + { url = "https://files.pythonhosted.org/packages/04/03/5159321baae9b2222442a70c1f988cbbd66b9be0675dd3936461269be360/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:c1476d6f29eb81aa4151c9a31219b03f1f798dc43d8af1250a870735516a1212", size = 592623, upload-time = "2025-08-27T12:14:33.543Z" }, + { url = "https://files.pythonhosted.org/packages/ff/39/c09fd1ad28b85bc1d4554a8710233c9f4cefd03d7717a1b8fbfd171d1167/rpds_py-0.27.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:3ce0cac322b0d69b63c9cdb895ee1b65805ec9ffad37639f291dd79467bee675", size = 558800, upload-time = "2025-08-27T12:14:35.436Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d6/99228e6bbcf4baa764b18258f519a9035131d91b538d4e0e294313462a98/rpds_py-0.27.1-cp314-cp314-win32.whl", hash = "sha256:dfbfac137d2a3d0725758cd141f878bf4329ba25e34979797c89474a89a8a3a3", size = 221943, upload-time = "2025-08-27T12:14:36.898Z" }, + { url = "https://files.pythonhosted.org/packages/be/07/c802bc6b8e95be83b79bdf23d1aa61d68324cb1006e245d6c58e959e314d/rpds_py-0.27.1-cp314-cp314-win_amd64.whl", hash = "sha256:a6e57b0abfe7cc513450fcf529eb486b6e4d3f8aee83e92eb5f1ef848218d456", size = 233739, upload-time = "2025-08-27T12:14:38.386Z" }, + { url = "https://files.pythonhosted.org/packages/c8/89/3e1b1c16d4c2d547c5717377a8df99aee8099ff050f87c45cb4d5fa70891/rpds_py-0.27.1-cp314-cp314-win_arm64.whl", hash = "sha256:faf8d146f3d476abfee026c4ae3bdd9ca14236ae4e4c310cbd1cf75ba33d24a3", size = 223120, upload-time = "2025-08-27T12:14:39.82Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/dc7931dc2fa4a6e46b2a4fa744a9fe5c548efd70e0ba74f40b39fa4a8c10/rpds_py-0.27.1-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:ba81d2b56b6d4911ce735aad0a1d4495e808b8ee4dc58715998741a26874e7c2", size = 358944, upload-time = "2025-08-27T12:14:41.199Z" }, + { url = "https://files.pythonhosted.org/packages/e6/22/4af76ac4e9f336bfb1a5f240d18a33c6b2fcaadb7472ac7680576512b49a/rpds_py-0.27.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:84f7d509870098de0e864cad0102711c1e24e9b1a50ee713b65928adb22269e4", size = 342283, upload-time = "2025-08-27T12:14:42.699Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/2a7c619b3c2272ea9feb9ade67a45c40b3eeb500d503ad4c28c395dc51b4/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e960fc78fecd1100539f14132425e1d5fe44ecb9239f8f27f079962021523e", size = 380320, upload-time = "2025-08-27T12:14:44.157Z" }, + { url = "https://files.pythonhosted.org/packages/a2/7d/4c6d243ba4a3057e994bb5bedd01b5c963c12fe38dde707a52acdb3849e7/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:62f85b665cedab1a503747617393573995dac4600ff51869d69ad2f39eb5e817", size = 391760, upload-time = "2025-08-27T12:14:45.845Z" }, + { url = "https://files.pythonhosted.org/packages/b4/71/b19401a909b83bcd67f90221330bc1ef11bc486fe4e04c24388d28a618ae/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fed467af29776f6556250c9ed85ea5a4dd121ab56a5f8b206e3e7a4c551e48ec", size = 522476, upload-time = "2025-08-27T12:14:47.364Z" }, + { url = "https://files.pythonhosted.org/packages/e4/44/1a3b9715c0455d2e2f0f6df5ee6d6f5afdc423d0773a8a682ed2b43c566c/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2729615f9d430af0ae6b36cf042cb55c0936408d543fb691e1a9e36648fd35a", size = 403418, upload-time = "2025-08-27T12:14:49.991Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4b/fb6c4f14984eb56673bc868a66536f53417ddb13ed44b391998100a06a96/rpds_py-0.27.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b207d881a9aef7ba753d69c123a35d96ca7cb808056998f6b9e8747321f03b8", size = 384771, upload-time = "2025-08-27T12:14:52.159Z" }, + { url = "https://files.pythonhosted.org/packages/c0/56/d5265d2d28b7420d7b4d4d85cad8ef891760f5135102e60d5c970b976e41/rpds_py-0.27.1-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:639fd5efec029f99b79ae47e5d7e00ad8a773da899b6309f6786ecaf22948c48", size = 400022, upload-time = "2025-08-27T12:14:53.859Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e9/9f5fc70164a569bdd6ed9046486c3568d6926e3a49bdefeeccfb18655875/rpds_py-0.27.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fecc80cb2a90e28af8a9b366edacf33d7a91cbfe4c2c4544ea1246e949cfebeb", size = 416787, upload-time = "2025-08-27T12:14:55.673Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/56dd03430ba491db943a81dcdef115a985aac5f44f565cd39a00c766d45c/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42a89282d711711d0a62d6f57d81aa43a1368686c45bc1c46b7f079d55692734", size = 557538, upload-time = "2025-08-27T12:14:57.245Z" }, + { url = "https://files.pythonhosted.org/packages/3f/36/92cc885a3129993b1d963a2a42ecf64e6a8e129d2c7cc980dbeba84e55fb/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:cf9931f14223de59551ab9d38ed18d92f14f055a5f78c1d8ad6493f735021bbb", size = 588512, upload-time = "2025-08-27T12:14:58.728Z" }, + { url = "https://files.pythonhosted.org/packages/dd/10/6b283707780a81919f71625351182b4f98932ac89a09023cb61865136244/rpds_py-0.27.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f39f58a27cc6e59f432b568ed8429c7e1641324fbe38131de852cd77b2d534b0", size = 555813, upload-time = "2025-08-27T12:15:00.334Z" }, + { url = "https://files.pythonhosted.org/packages/04/2e/30b5ea18c01379da6272a92825dd7e53dc9d15c88a19e97932d35d430ef7/rpds_py-0.27.1-cp314-cp314t-win32.whl", hash = "sha256:d5fa0ee122dc09e23607a28e6d7b150da16c662e66409bbe85230e4c85bb528a", size = 217385, upload-time = "2025-08-27T12:15:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/7d/97119da51cb1dd3f2f3c0805f155a3aa4a95fa44fe7d78ae15e69edf4f34/rpds_py-0.27.1-cp314-cp314t-win_amd64.whl", hash = "sha256:6567d2bb951e21232c2f660c24cf3470bb96de56cdcb3f071a83feeaff8a2772", size = 230097, upload-time = "2025-08-27T12:15:03.961Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ed/e1fba02de17f4f76318b834425257c8ea297e415e12c68b4361f63e8ae92/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cdfe4bb2f9fe7458b7453ad3c33e726d6d1c7c0a72960bcc23800d77384e42df", size = 371402, upload-time = "2025-08-27T12:15:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/af/7c/e16b959b316048b55585a697e94add55a4ae0d984434d279ea83442e460d/rpds_py-0.27.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8fabb8fd848a5f75a2324e4a84501ee3a5e3c78d8603f83475441866e60b94a3", size = 354084, upload-time = "2025-08-27T12:15:53.219Z" }, + { url = "https://files.pythonhosted.org/packages/de/c1/ade645f55de76799fdd08682d51ae6724cb46f318573f18be49b1e040428/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda8719d598f2f7f3e0f885cba8646644b55a187762bec091fa14a2b819746a9", size = 383090, upload-time = "2025-08-27T12:15:55.158Z" }, + { url = "https://files.pythonhosted.org/packages/1f/27/89070ca9b856e52960da1472efcb6c20ba27cfe902f4f23ed095b9cfc61d/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c64d07e95606ec402a0a1c511fe003873fa6af630bda59bac77fac8b4318ebc", size = 394519, upload-time = "2025-08-27T12:15:57.238Z" }, + { url = "https://files.pythonhosted.org/packages/b3/28/be120586874ef906aa5aeeae95ae8df4184bc757e5b6bd1c729ccff45ed5/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:93a2ed40de81bcff59aabebb626562d48332f3d028ca2036f1d23cbb52750be4", size = 523817, upload-time = "2025-08-27T12:15:59.237Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/70cc197bc11cfcde02a86f36ac1eed15c56667c2ebddbdb76a47e90306da/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:387ce8c44ae94e0ec50532d9cb0edce17311024c9794eb196b90e1058aadeb66", size = 403240, upload-time = "2025-08-27T12:16:00.923Z" }, + { url = "https://files.pythonhosted.org/packages/cf/35/46936cca449f7f518f2f4996e0e8344db4b57e2081e752441154089d2a5f/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aaf94f812c95b5e60ebaf8bfb1898a7d7cb9c1af5744d4a67fa47796e0465d4e", size = 385194, upload-time = "2025-08-27T12:16:02.802Z" }, + { url = "https://files.pythonhosted.org/packages/e1/62/29c0d3e5125c3270b51415af7cbff1ec587379c84f55a5761cc9efa8cd06/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4848ca84d6ded9b58e474dfdbad4b8bfb450344c0551ddc8d958bf4b36aa837c", size = 402086, upload-time = "2025-08-27T12:16:04.806Z" }, + { url = "https://files.pythonhosted.org/packages/8f/66/03e1087679227785474466fdd04157fb793b3b76e3fcf01cbf4c693c1949/rpds_py-0.27.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bde09cbcf2248b73c7c323be49b280180ff39fadcfe04e7b6f54a678d02a7cf", size = 419272, upload-time = "2025-08-27T12:16:06.471Z" }, + { url = "https://files.pythonhosted.org/packages/6a/24/e3e72d265121e00b063aef3e3501e5b2473cf1b23511d56e529531acf01e/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:94c44ee01fd21c9058f124d2d4f0c9dc7634bec93cd4b38eefc385dabe71acbf", size = 560003, upload-time = "2025-08-27T12:16:08.06Z" }, + { url = "https://files.pythonhosted.org/packages/26/ca/f5a344c534214cc2d41118c0699fffbdc2c1bc7046f2a2b9609765ab9c92/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:df8b74962e35c9249425d90144e721eed198e6555a0e22a563d29fe4486b51f6", size = 590482, upload-time = "2025-08-27T12:16:10.137Z" }, + { url = "https://files.pythonhosted.org/packages/ce/08/4349bdd5c64d9d193c360aa9db89adeee6f6682ab8825dca0a3f535f434f/rpds_py-0.27.1-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:dc23e6820e3b40847e2f4a7726462ba0cf53089512abe9ee16318c366494c17a", size = 556523, upload-time = "2025-08-27T12:16:12.188Z" }, +] + +[[package]] +name = "rsa" +version = "4.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/da/8a/22b7beea3ee0d44b1916c0c1cb0ee3af23b700b6da9f04991899d0c555d4/rsa-4.9.1.tar.gz", hash = "sha256:e7bdbfdb5497da4c07dfd35530e1a902659db6ff241e39d9953cad06ebd0ae75", size = 29034, upload-time = "2025-04-16T09:51:18.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/58/6ca66896635352812de66f71cdf9ff86b3a4f79071ca5730088c0cd0fc8d/ruff-0.14.1.tar.gz", hash = "sha256:1dd86253060c4772867c61791588627320abcb6ed1577a90ef432ee319729b69", size = 5513429, upload-time = "2025-10-16T18:05:41.766Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8d/39/9cc5ab181478d7a18adc1c1e051a84ee02bec94eb9bdfd35643d7c74ca31/ruff-0.14.1-py3-none-linux_armv6l.whl", hash = "sha256:083bfc1f30f4a391ae09c6f4f99d83074416b471775b59288956f5bc18e82f8b", size = 12445415, upload-time = "2025-10-16T18:04:48.227Z" }, + { url = "https://files.pythonhosted.org/packages/ef/2e/1226961855ccd697255988f5a2474890ac7c5863b080b15bd038df820818/ruff-0.14.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f6fa757cd717f791009f7669fefb09121cc5f7d9bd0ef211371fad68c2b8b224", size = 12784267, upload-time = "2025-10-16T18:04:52.515Z" }, + { url = "https://files.pythonhosted.org/packages/c1/ea/fd9e95863124ed159cd0667ec98449ae461de94acda7101f1acb6066da00/ruff-0.14.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d6191903d39ac156921398e9c86b7354d15e3c93772e7dbf26c9fcae59ceccd5", size = 11781872, upload-time = "2025-10-16T18:04:55.396Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5a/e890f7338ff537dba4589a5e02c51baa63020acfb7c8cbbaea4831562c96/ruff-0.14.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed04f0e04f7a4587244e5c9d7df50e6b5bf2705d75059f409a6421c593a35896", size = 12226558, upload-time = "2025-10-16T18:04:58.166Z" }, + { url = "https://files.pythonhosted.org/packages/a6/7a/8ab5c3377f5bf31e167b73651841217542bcc7aa1c19e83030835cc25204/ruff-0.14.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5c9e6cf6cd4acae0febbce29497accd3632fe2025c0c583c8b87e8dbdeae5f61", size = 12187898, upload-time = "2025-10-16T18:05:01.455Z" }, + { url = "https://files.pythonhosted.org/packages/48/8d/ba7c33aa55406955fc124e62c8259791c3d42e3075a71710fdff9375134f/ruff-0.14.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fa2458527794ecdfbe45f654e42c61f2503a230545a91af839653a0a93dbc6", size = 12939168, upload-time = "2025-10-16T18:05:04.397Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c2/70783f612b50f66d083380e68cbd1696739d88e9b4f6164230375532c637/ruff-0.14.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:39f1c392244e338b21d42ab29b8a6392a722c5090032eb49bb4d6defcdb34345", size = 14386942, upload-time = "2025-10-16T18:05:07.102Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/cd7abb9c776b66d332119d67f96acf15830d120f5b884598a36d9d3f4d83/ruff-0.14.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7382fa12a26cce1f95070ce450946bec357727aaa428983036362579eadcc5cf", size = 13990622, upload-time = "2025-10-16T18:05:09.882Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/4259b696db12ac152fe472764b4f78bbdd9b477afd9bc3a6d53c01300b37/ruff-0.14.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd0bf2be3ae8521e1093a487c4aa3b455882f139787770698530d28ed3fbb37c", size = 13431143, upload-time = "2025-10-16T18:05:13.46Z" }, + { url = "https://files.pythonhosted.org/packages/e0/35/266a80d0eb97bd224b3265b9437bd89dde0dcf4faf299db1212e81824e7e/ruff-0.14.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cabcaa9ccf8089fb4fdb78d17cc0e28241520f50f4c2e88cb6261ed083d85151", size = 13132844, upload-time = "2025-10-16T18:05:16.1Z" }, + { url = "https://files.pythonhosted.org/packages/65/6e/d31ce218acc11a8d91ef208e002a31acf315061a85132f94f3df7a252b18/ruff-0.14.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:747d583400f6125ec11a4c14d1c8474bf75d8b419ad22a111a537ec1a952d192", size = 13401241, upload-time = "2025-10-16T18:05:19.395Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b5/dbc4221bf0b03774b3b2f0d47f39e848d30664157c15b965a14d890637d2/ruff-0.14.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5a6e74c0efd78515a1d13acbfe6c90f0f5bd822aa56b4a6d43a9ffb2ae6e56cd", size = 12132476, upload-time = "2025-10-16T18:05:22.163Z" }, + { url = "https://files.pythonhosted.org/packages/98/4b/ac99194e790ccd092d6a8b5f341f34b6e597d698e3077c032c502d75ea84/ruff-0.14.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0ea6a864d2fb41a4b6d5b456ed164302a0d96f4daac630aeba829abfb059d020", size = 12139749, upload-time = "2025-10-16T18:05:25.162Z" }, + { url = "https://files.pythonhosted.org/packages/47/26/7df917462c3bb5004e6fdfcc505a49e90bcd8a34c54a051953118c00b53a/ruff-0.14.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:0826b8764f94229604fa255918d1cc45e583e38c21c203248b0bfc9a0e930be5", size = 12544758, upload-time = "2025-10-16T18:05:28.018Z" }, + { url = "https://files.pythonhosted.org/packages/64/d0/81e7f0648e9764ad9b51dd4be5e5dac3fcfff9602428ccbae288a39c2c22/ruff-0.14.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:cbc52160465913a1a3f424c81c62ac8096b6a491468e7d872cb9444a860bc33d", size = 13221811, upload-time = "2025-10-16T18:05:30.707Z" }, + { url = "https://files.pythonhosted.org/packages/c3/07/3c45562c67933cc35f6d5df4ca77dabbcd88fddaca0d6b8371693d29fd56/ruff-0.14.1-py3-none-win32.whl", hash = "sha256:e037ea374aaaff4103240ae79168c0945ae3d5ae8db190603de3b4012bd1def6", size = 12319467, upload-time = "2025-10-16T18:05:33.261Z" }, + { url = "https://files.pythonhosted.org/packages/02/88/0ee4ca507d4aa05f67e292d2e5eb0b3e358fbcfe527554a2eda9ac422d6b/ruff-0.14.1-py3-none-win_amd64.whl", hash = "sha256:59d599cdff9c7f925a017f6f2c256c908b094e55967f93f2821b1439928746a1", size = 13401123, upload-time = "2025-10-16T18:05:35.984Z" }, + { url = "https://files.pythonhosted.org/packages/b8/81/4b6387be7014858d924b843530e1b2a8e531846807516e9bea2ee0936bf7/ruff-0.14.1-py3-none-win_arm64.whl", hash = "sha256:e3b443c4c9f16ae850906b8d0a707b2a4c16f8d2f0a7fe65c475c5886665ce44", size = 12436636, upload-time = "2025-10-16T18:05:38.995Z" }, +] + +[[package]] +name = "s3transfer" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547, upload-time = "2025-09-09T19:23:31.089Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712, upload-time = "2025-09-09T19:23:30.041Z" }, +] + +[[package]] +name = "safehttpx" +version = "0.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/4c/19db75e6405692b2a96af8f06d1258f8aa7290bdc35ac966f03e207f6d7f/safehttpx-0.1.6.tar.gz", hash = "sha256:b356bfc82cee3a24c395b94a2dbeabbed60aff1aa5fa3b5fe97c4f2456ebce42", size = 9987, upload-time = "2024-12-02T18:44:10.226Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/c0/1108ad9f01567f66b3154063605b350b69c3c9366732e09e45f9fd0d1deb/safehttpx-0.1.6-py3-none-any.whl", hash = "sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c", size = 8692, upload-time = "2024-12-02T18:44:08.555Z" }, +] + +[[package]] +name = "screeninfo" +version = "0.8.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cython", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ec/bb/e69e5e628d43f118e0af4fc063c20058faa8635c95a1296764acc8167e27/screeninfo-0.8.1.tar.gz", hash = "sha256:9983076bcc7e34402a1a9e4d7dabf3729411fd2abb3f3b4be7eba73519cd2ed1", size = 10666, upload-time = "2022-09-09T11:35:23.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/bf/c5205d480307bef660e56544b9e3d7ff687da776abb30c9cb3f330887570/screeninfo-0.8.1-py3-none-any.whl", hash = "sha256:e97d6b173856edcfa3bd282f81deb528188aff14b11ec3e195584e7641be733c", size = 12907, upload-time = "2022-09-09T11:35:21.351Z" }, +] + +[[package]] +name = "semantic-version" +version = "2.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/31/f2289ce78b9b473d582568c234e104d2a342fd658cc288a7553d83bb8595/semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c", size = 52289, upload-time = "2022-05-26T13:35:23.454Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/23/8146aad7d88f4fcb3a6218f41a60f6c2d4e3a72de72da1825dc7c8f7877c/semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177", size = 15552, upload-time = "2022-05-26T13:35:21.206Z" }, +] + +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "soupsieve" +version = "2.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/e6/21ccce3262dd4889aa3332e5a119a3491a95e8f60939870a3a035aabac0d/soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f", size = 103472, upload-time = "2025-08-27T15:39:51.78Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/a0/bb38d3b76b8cae341dad93a2dd83ab7462e6dbcdd84d43f54ee60a8dc167/soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c", size = 36679, upload-time = "2025-08-27T15:39:50.179Z" }, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.44" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/f2/840d7b9496825333f532d2e3976b8eadbf52034178aac53630d09fe6e1ef/sqlalchemy-2.0.44.tar.gz", hash = "sha256:0ae7454e1ab1d780aee69fd2aae7d6b8670a581d8847f2d1e0f7ddfbf47e5a22", size = 9819830, upload-time = "2025-10-10T14:39:12.935Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e3/81/15d7c161c9ddf0900b076b55345872ed04ff1ed6a0666e5e94ab44b0163c/sqlalchemy-2.0.44-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0fe3917059c7ab2ee3f35e77757062b1bea10a0b6ca633c58391e3f3c6c488dd", size = 2140517, upload-time = "2025-10-10T15:36:15.64Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d5/4abd13b245c7d91bdf131d4916fd9e96a584dac74215f8b5bc945206a974/sqlalchemy-2.0.44-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:de4387a354ff230bc979b46b2207af841dc8bf29847b6c7dbe60af186d97aefa", size = 2130738, upload-time = "2025-10-10T15:36:16.91Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3c/8418969879c26522019c1025171cefbb2a8586b6789ea13254ac602986c0/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3678a0fb72c8a6a29422b2732fe423db3ce119c34421b5f9955873eb9b62c1e", size = 3304145, upload-time = "2025-10-10T15:34:19.569Z" }, + { url = "https://files.pythonhosted.org/packages/94/2d/fdb9246d9d32518bda5d90f4b65030b9bf403a935cfe4c36a474846517cb/sqlalchemy-2.0.44-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cf6872a23601672d61a68f390e44703442639a12ee9dd5a88bbce52a695e46e", size = 3304511, upload-time = "2025-10-10T15:47:05.088Z" }, + { url = "https://files.pythonhosted.org/packages/7d/fb/40f2ad1da97d5c83f6c1269664678293d3fe28e90ad17a1093b735420549/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:329aa42d1be9929603f406186630135be1e7a42569540577ba2c69952b7cf399", size = 3235161, upload-time = "2025-10-10T15:34:21.193Z" }, + { url = "https://files.pythonhosted.org/packages/95/cb/7cf4078b46752dca917d18cf31910d4eff6076e5b513c2d66100c4293d83/sqlalchemy-2.0.44-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:70e03833faca7166e6a9927fbee7c27e6ecde436774cd0b24bbcc96353bce06b", size = 3261426, upload-time = "2025-10-10T15:47:07.196Z" }, + { url = "https://files.pythonhosted.org/packages/f8/3b/55c09b285cb2d55bdfa711e778bdffdd0dc3ffa052b0af41f1c5d6e582fa/sqlalchemy-2.0.44-cp311-cp311-win32.whl", hash = "sha256:253e2f29843fb303eca6b2fc645aca91fa7aa0aa70b38b6950da92d44ff267f3", size = 2105392, upload-time = "2025-10-10T15:38:20.051Z" }, + { url = "https://files.pythonhosted.org/packages/c7/23/907193c2f4d680aedbfbdf7bf24c13925e3c7c292e813326c1b84a0b878e/sqlalchemy-2.0.44-cp311-cp311-win_amd64.whl", hash = "sha256:7a8694107eb4308a13b425ca8c0e67112f8134c846b6e1f722698708741215d5", size = 2130293, upload-time = "2025-10-10T15:38:21.601Z" }, + { url = "https://files.pythonhosted.org/packages/62/c4/59c7c9b068e6813c898b771204aad36683c96318ed12d4233e1b18762164/sqlalchemy-2.0.44-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72fea91746b5890f9e5e0997f16cbf3d53550580d76355ba2d998311b17b2250", size = 2139675, upload-time = "2025-10-10T16:03:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/d6/ae/eeb0920537a6f9c5a3708e4a5fc55af25900216bdb4847ec29cfddf3bf3a/sqlalchemy-2.0.44-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:585c0c852a891450edbb1eaca8648408a3cc125f18cf433941fa6babcc359e29", size = 2127726, upload-time = "2025-10-10T16:03:35.934Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d5/2ebbabe0379418eda8041c06b0b551f213576bfe4c2f09d77c06c07c8cc5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b94843a102efa9ac68a7a30cd46df3ff1ed9c658100d30a725d10d9c60a2f44", size = 3327603, upload-time = "2025-10-10T15:35:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/5aa65852dadc24b7d8ae75b7efb8d19303ed6ac93482e60c44a585930ea5/sqlalchemy-2.0.44-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:119dc41e7a7defcefc57189cfa0e61b1bf9c228211aba432b53fb71ef367fda1", size = 3337842, upload-time = "2025-10-10T15:43:45.431Z" }, + { url = "https://files.pythonhosted.org/packages/41/92/648f1afd3f20b71e880ca797a960f638d39d243e233a7082c93093c22378/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0765e318ee9179b3718c4fd7ba35c434f4dd20332fbc6857a5e8df17719c24d7", size = 3264558, upload-time = "2025-10-10T15:35:29.93Z" }, + { url = "https://files.pythonhosted.org/packages/40/cf/e27d7ee61a10f74b17740918e23cbc5bc62011b48282170dc4c66da8ec0f/sqlalchemy-2.0.44-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2e7b5b079055e02d06a4308d0481658e4f06bc7ef211567edc8f7d5dce52018d", size = 3301570, upload-time = "2025-10-10T15:43:48.407Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3d/3116a9a7b63e780fb402799b6da227435be878b6846b192f076d2f838654/sqlalchemy-2.0.44-cp312-cp312-win32.whl", hash = "sha256:846541e58b9a81cce7dee8329f352c318de25aa2f2bbe1e31587eb1f057448b4", size = 2103447, upload-time = "2025-10-10T15:03:21.678Z" }, + { url = "https://files.pythonhosted.org/packages/25/83/24690e9dfc241e6ab062df82cc0df7f4231c79ba98b273fa496fb3dd78ed/sqlalchemy-2.0.44-cp312-cp312-win_amd64.whl", hash = "sha256:7cbcb47fd66ab294703e1644f78971f6f2f1126424d2b300678f419aa73c7b6e", size = 2130912, upload-time = "2025-10-10T15:03:24.656Z" }, + { url = "https://files.pythonhosted.org/packages/45/d3/c67077a2249fdb455246e6853166360054c331db4613cda3e31ab1cadbef/sqlalchemy-2.0.44-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff486e183d151e51b1d694c7aa1695747599bb00b9f5f604092b54b74c64a8e1", size = 2135479, upload-time = "2025-10-10T16:03:37.671Z" }, + { url = "https://files.pythonhosted.org/packages/2b/91/eabd0688330d6fd114f5f12c4f89b0d02929f525e6bf7ff80aa17ca802af/sqlalchemy-2.0.44-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b1af8392eb27b372ddb783b317dea0f650241cea5bd29199b22235299ca2e45", size = 2123212, upload-time = "2025-10-10T16:03:41.755Z" }, + { url = "https://files.pythonhosted.org/packages/b0/bb/43e246cfe0e81c018076a16036d9b548c4cc649de241fa27d8d9ca6f85ab/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b61188657e3a2b9ac4e8f04d6cf8e51046e28175f79464c67f2fd35bceb0976", size = 3255353, upload-time = "2025-10-10T15:35:31.221Z" }, + { url = "https://files.pythonhosted.org/packages/b9/96/c6105ed9a880abe346b64d3b6ddef269ddfcab04f7f3d90a0bf3c5a88e82/sqlalchemy-2.0.44-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b87e7b91a5d5973dda5f00cd61ef72ad75a1db73a386b62877d4875a8840959c", size = 3260222, upload-time = "2025-10-10T15:43:50.124Z" }, + { url = "https://files.pythonhosted.org/packages/44/16/1857e35a47155b5ad927272fee81ae49d398959cb749edca6eaa399b582f/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:15f3326f7f0b2bfe406ee562e17f43f36e16167af99c4c0df61db668de20002d", size = 3189614, upload-time = "2025-10-10T15:35:32.578Z" }, + { url = "https://files.pythonhosted.org/packages/88/ee/4afb39a8ee4fc786e2d716c20ab87b5b1fb33d4ac4129a1aaa574ae8a585/sqlalchemy-2.0.44-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e77faf6ff919aa8cd63f1c4e561cac1d9a454a191bb864d5dd5e545935e5a40", size = 3226248, upload-time = "2025-10-10T15:43:51.862Z" }, + { url = "https://files.pythonhosted.org/packages/32/d5/0e66097fc64fa266f29a7963296b40a80d6a997b7ac13806183700676f86/sqlalchemy-2.0.44-cp313-cp313-win32.whl", hash = "sha256:ee51625c2d51f8baadf2829fae817ad0b66b140573939dd69284d2ba3553ae73", size = 2101275, upload-time = "2025-10-10T15:03:26.096Z" }, + { url = "https://files.pythonhosted.org/packages/03/51/665617fe4f8c6450f42a6d8d69243f9420f5677395572c2fe9d21b493b7b/sqlalchemy-2.0.44-cp313-cp313-win_amd64.whl", hash = "sha256:c1c80faaee1a6c3428cecf40d16a2365bcf56c424c92c2b6f0f9ad204b899e9e", size = 2127901, upload-time = "2025-10-10T15:03:27.548Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5e/6a29fa884d9fb7ddadf6b69490a9d45fded3b38541713010dad16b77d015/sqlalchemy-2.0.44-py3-none-any.whl", hash = "sha256:19de7ca1246fbef9f9d1bff8f1ab25641569df226364a0e40457dc5457c54b05", size = 1928718, upload-time = "2025-10-10T15:29:45.32Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/6f/22ed6e33f8a9e76ca0a412405f31abb844b779d52c5f96660766edcd737c/sse_starlette-3.0.2.tar.gz", hash = "sha256:ccd60b5765ebb3584d0de2d7a6e4f745672581de4f5005ab31c3a25d10b52b3a", size = 20985, upload-time = "2025-07-27T09:07:44.565Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/10/c78f463b4ef22eef8491f218f692be838282cd65480f6e423d7730dfd1fb/sse_starlette-3.0.2-py3-none-any.whl", hash = "sha256:16b7cbfddbcd4eaca11f7b586f3b8a080f1afe952c15813455b162edea619e5a", size = 11297, upload-time = "2025-07-27T09:07:43.268Z" }, +] + +[[package]] +name = "starlette" +version = "0.48.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/a5/d6f429d43394057b67a6b5bbe6eae2f77a6bf7459d961fdb224bf206eee6/starlette-0.48.0.tar.gz", hash = "sha256:7e8cee469a8ab2352911528110ce9088fdc6a37d9876926e73da7ce4aa4c7a46", size = 2652949, upload-time = "2025-09-13T08:41:05.699Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/72/2db2f49247d0a18b4f1bb9a5a39a0162869acf235f3a96418363947b3d46/starlette-0.48.0-py3-none-any.whl", hash = "sha256:0764ca97b097582558ecb498132ed0c7d942f233f365b86ba37770e026510659", size = 73736, upload-time = "2025-09-13T08:41:03.869Z" }, +] + +[[package]] +name = "tabulate" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/46/21ea696b21f1d6d1efec8639c204bdf20fde8bafb351e1355c72c5d7de52/tiktoken-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e227c7f96925003487c33b1b32265fad2fbcec2b7cf4817afb76d416f40f6bb", size = 1051565, upload-time = "2025-10-06T20:21:44.566Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d9/35c5d2d9e22bb2a5f74ba48266fb56c63d76ae6f66e02feb628671c0283e/tiktoken-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c06cf0fcc24c2cb2adb5e185c7082a82cba29c17575e828518c2f11a01f445aa", size = 995284, upload-time = "2025-10-06T20:21:45.622Z" }, + { url = "https://files.pythonhosted.org/packages/01/84/961106c37b8e49b9fdcf33fe007bb3a8fdcc380c528b20cc7fbba80578b8/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f18f249b041851954217e9fd8e5c00b024ab2315ffda5ed77665a05fa91f42dc", size = 1129201, upload-time = "2025-10-06T20:21:47.074Z" }, + { url = "https://files.pythonhosted.org/packages/6a/d0/3d9275198e067f8b65076a68894bb52fd253875f3644f0a321a720277b8a/tiktoken-0.12.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:47a5bc270b8c3db00bb46ece01ef34ad050e364b51d406b6f9730b64ac28eded", size = 1152444, upload-time = "2025-10-06T20:21:48.139Z" }, + { url = "https://files.pythonhosted.org/packages/78/db/a58e09687c1698a7c592e1038e01c206569b86a0377828d51635561f8ebf/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:508fa71810c0efdcd1b898fda574889ee62852989f7c1667414736bcb2b9a4bd", size = 1195080, upload-time = "2025-10-06T20:21:49.246Z" }, + { url = "https://files.pythonhosted.org/packages/9e/1b/a9e4d2bf91d515c0f74afc526fd773a812232dd6cda33ebea7f531202325/tiktoken-0.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a1af81a6c44f008cba48494089dd98cccb8b313f55e961a52f5b222d1e507967", size = 1255240, upload-time = "2025-10-06T20:21:50.274Z" }, + { url = "https://files.pythonhosted.org/packages/9d/15/963819345f1b1fb0809070a79e9dd96938d4ca41297367d471733e79c76c/tiktoken-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e68e3e593637b53e56f7237be560f7a394451cb8c11079755e80ae64b9e6def", size = 879422, upload-time = "2025-10-06T20:21:51.734Z" }, + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, +] + +[[package]] +name = "tld" +version = "0.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/a1/5723b07a70c1841a80afc9ac572fdf53488306848d844cd70519391b0d26/tld-0.13.1.tar.gz", hash = "sha256:75ec00936cbcf564f67361c41713363440b6c4ef0f0c1592b5b0fbe72c17a350", size = 462000, upload-time = "2025-05-21T22:18:29.341Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/70/b2f38360c3fc4bc9b5e8ef429e1fde63749144ac583c2dbdf7e21e27a9ad/tld-0.13.1-py2.py3-none-any.whl", hash = "sha256:a2d35109433ac83486ddf87e3c4539ab2c5c2478230e5d9c060a18af4b03aa7c", size = 274718, upload-time = "2025-05-21T22:18:25.811Z" }, +] + +[[package]] +name = "tokenizers" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "huggingface-hub" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1c/46/fb6854cec3278fbfa4a75b50232c77622bc517ac886156e6afbfa4d8fc6e/tokenizers-0.22.1.tar.gz", hash = "sha256:61de6522785310a309b3407bac22d99c4db5dba349935e99e4d15ea2226af2d9", size = 363123, upload-time = "2025-09-19T09:49:23.424Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/33/f4b2d94ada7ab297328fc671fed209368ddb82f965ec2224eb1892674c3a/tokenizers-0.22.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:59fdb013df17455e5f950b4b834a7b3ee2e0271e6378ccb33aa74d178b513c73", size = 3069318, upload-time = "2025-09-19T09:49:11.848Z" }, + { url = "https://files.pythonhosted.org/packages/1c/58/2aa8c874d02b974990e89ff95826a4852a8b2a273c7d1b4411cdd45a4565/tokenizers-0.22.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8d4e484f7b0827021ac5f9f71d4794aaef62b979ab7608593da22b1d2e3c4edc", size = 2926478, upload-time = "2025-09-19T09:49:09.759Z" }, + { url = "https://files.pythonhosted.org/packages/1e/3b/55e64befa1e7bfea963cf4b787b2cea1011362c4193f5477047532ce127e/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19d2962dd28bc67c1f205ab180578a78eef89ac60ca7ef7cbe9635a46a56422a", size = 3256994, upload-time = "2025-09-19T09:48:56.701Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/fbfecf42f67d9b7b80fde4aabb2b3110a97fac6585c9470b5bff103a80cb/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:38201f15cdb1f8a6843e6563e6e79f4abd053394992b9bbdf5213ea3469b4ae7", size = 3153141, upload-time = "2025-09-19T09:48:59.749Z" }, + { url = "https://files.pythonhosted.org/packages/17/a9/b38f4e74e0817af8f8ef925507c63c6ae8171e3c4cb2d5d4624bf58fca69/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1cbe5454c9a15df1b3443c726063d930c16f047a3cc724b9e6e1a91140e5a21", size = 3508049, upload-time = "2025-09-19T09:49:05.868Z" }, + { url = "https://files.pythonhosted.org/packages/d2/48/dd2b3dac46bb9134a88e35d72e1aa4869579eacc1a27238f1577270773ff/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e7d094ae6312d69cc2a872b54b91b309f4f6fbce871ef28eb27b52a98e4d0214", size = 3710730, upload-time = "2025-09-19T09:49:01.832Z" }, + { url = "https://files.pythonhosted.org/packages/93/0e/ccabc8d16ae4ba84a55d41345207c1e2ea88784651a5a487547d80851398/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:afd7594a56656ace95cdd6df4cca2e4059d294c5cfb1679c57824b605556cb2f", size = 3412560, upload-time = "2025-09-19T09:49:03.867Z" }, + { url = "https://files.pythonhosted.org/packages/d0/c6/dc3a0db5a6766416c32c034286d7c2d406da1f498e4de04ab1b8959edd00/tokenizers-0.22.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2ef6063d7a84994129732b47e7915e8710f27f99f3a3260b8a38fc7ccd083f4", size = 3250221, upload-time = "2025-09-19T09:49:07.664Z" }, + { url = "https://files.pythonhosted.org/packages/d7/a6/2c8486eef79671601ff57b093889a345dd3d576713ef047776015dc66de7/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ba0a64f450b9ef412c98f6bcd2a50c6df6e2443b560024a09fa6a03189726879", size = 9345569, upload-time = "2025-09-19T09:49:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/6b/16/32ce667f14c35537f5f605fe9bea3e415ea1b0a646389d2295ec348d5657/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:331d6d149fa9c7d632cde4490fb8bbb12337fa3a0232e77892be656464f4b446", size = 9271599, upload-time = "2025-09-19T09:49:16.639Z" }, + { url = "https://files.pythonhosted.org/packages/51/7c/a5f7898a3f6baa3fc2685c705e04c98c1094c523051c805cdd9306b8f87e/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:607989f2ea68a46cb1dfbaf3e3aabdf3f21d8748312dbeb6263d1b3b66c5010a", size = 9533862, upload-time = "2025-09-19T09:49:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/36/65/7e75caea90bc73c1dd8d40438adf1a7bc26af3b8d0a6705ea190462506e1/tokenizers-0.22.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a0f307d490295717726598ef6fa4f24af9d484809223bbc253b201c740a06390", size = 9681250, upload-time = "2025-09-19T09:49:21.501Z" }, + { url = "https://files.pythonhosted.org/packages/30/2c/959dddef581b46e6209da82df3b78471e96260e2bc463f89d23b1bf0e52a/tokenizers-0.22.1-cp39-abi3-win32.whl", hash = "sha256:b5120eed1442765cd90b903bb6cfef781fd8fe64e34ccaecbae4c619b7b12a82", size = 2472003, upload-time = "2025-09-19T09:49:27.089Z" }, + { url = "https://files.pythonhosted.org/packages/b3/46/e33a8c93907b631a99377ef4c5f817ab453d0b34f93529421f42ff559671/tokenizers-0.22.1-cp39-abi3-win_amd64.whl", hash = "sha256:65fd6e3fb11ca1e78a6a93602490f134d1fdeb13bcef99389d5102ea318ed138", size = 2674684, upload-time = "2025-09-19T09:49:24.953Z" }, +] + +[[package]] +name = "tomlkit" +version = "0.13.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/18/0bbf3884e9eaa38819ebe46a7bd25dcd56b67434402b66a58c4b8e552575/tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1", size = 185207, upload-time = "2025-06-05T07:13:44.947Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bd/75/8539d011f6be8e29f339c42e633aae3cb73bffa95dd0f9adec09b9c58e85/tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0", size = 38901, upload-time = "2025-06-05T07:13:43.546Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, +] + +[[package]] +name = "trafilatura" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "courlan" }, + { name = "htmldate" }, + { name = "justext" }, + { name = "lxml" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/25/e3ebeefdebfdfae8c4a4396f5a6ea51fc6fa0831d63ce338e5090a8003dc/trafilatura-2.0.0.tar.gz", hash = "sha256:ceb7094a6ecc97e72fea73c7dba36714c5c5b577b6470e4520dca893706d6247", size = 253404, upload-time = "2024-12-03T15:23:24.16Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/b6/097367f180b6383a3581ca1b86fcae284e52075fa941d1232df35293363c/trafilatura-2.0.0-py3-none-any.whl", hash = "sha256:77eb5d1e993747f6f20938e1de2d840020719735690c840b9a1024803a4cd51d", size = 132557, upload-time = "2024-12-03T15:23:21.41Z" }, +] + +[[package]] +name = "ty" +version = "0.0.1a23" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/98/e9c6cc74e7f81d49f1c06db3a455a5bff6d9e47b73408d053e81daef77fb/ty-0.0.1a23.tar.gz", hash = "sha256:d3b4a81b47f306f571fd99bc71a4fa5607eae61079a18e77fadcf8401b19a6c9", size = 4360335, upload-time = "2025-10-16T18:18:59.475Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/45/d662cd4c0c5f6254c4ff0d05edad9cbbac23e01bb277602eaed276bb53ba/ty-0.0.1a23-py3-none-linux_armv6l.whl", hash = "sha256:7c76debd57623ac8712a9d2a32529a2b98915434aa3521cab92318bfe3f34dfc", size = 8735928, upload-time = "2025-10-16T18:18:23.161Z" }, + { url = "https://files.pythonhosted.org/packages/db/89/8aa7c303a55181fc121ecce143464a156b51f03481607ef0f58f67dc936c/ty-0.0.1a23-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:1d9b63c72cb94bcfe8f36b4527fd18abc46bdecc8f774001bcf7a8dd83e8c81a", size = 8584084, upload-time = "2025-10-16T18:18:25.579Z" }, + { url = "https://files.pythonhosted.org/packages/02/43/7a3bec50f440028153c0ee0044fd47e409372d41012f5f6073103a90beac/ty-0.0.1a23-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1a875135cdb77b60280eb74d3c97ce3c44f872bf4176f5e71602a0a9401341ca", size = 8061268, upload-time = "2025-10-16T18:18:27.668Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c2/75ddb10084cc7da8de077ae09fe5d8d76fec977c2ab71929c21b6fea622f/ty-0.0.1a23-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ddf5f4d057a023409a926e3be5ba0388aa8c93a01ddc6c87cca03af22c78a0c", size = 8319954, upload-time = "2025-10-16T18:18:29.54Z" }, + { url = "https://files.pythonhosted.org/packages/b2/57/0762763e9a29a1bd393b804a950c03d9ceb18aaf5e5baa7122afc50c2387/ty-0.0.1a23-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad89d894ef414d5607c3611ab68298581a444fd51570e0e4facdd7c8e8856748", size = 8550745, upload-time = "2025-10-16T18:18:31.548Z" }, + { url = "https://files.pythonhosted.org/packages/89/0a/855ca77e454955acddba2149ad7fe20fd24946289b8fd1d66b025b2afef1/ty-0.0.1a23-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6306ad146748390675871b0c7731e595ceb2241724bc7d2d46e56f392949fbb9", size = 8899930, upload-time = "2025-10-16T18:18:34.003Z" }, + { url = "https://files.pythonhosted.org/packages/ad/f0/9282da70da435d1890c5b1dff844a3139fc520d0a61747bb1e84fbf311d5/ty-0.0.1a23-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fa2155c0a66faeb515b88d7dc6b9f3fb393373798e97c01f05b1436c60d2c6b1", size = 9561714, upload-time = "2025-10-16T18:18:36.238Z" }, + { url = "https://files.pythonhosted.org/packages/b8/95/ffea2138629875a2083ccc64cc80585ecf0e487500835fe7c1b6f6305bf8/ty-0.0.1a23-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7d75d1f264afbe9a294d88e1e7736c003567a74f3a433c72231c36999a61e42", size = 9231064, upload-time = "2025-10-16T18:18:38.877Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/dac340d2d10e81788801e7580bad0168b190ba5a5c6cf6e4f798e094ee80/ty-0.0.1a23-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af8eb2341e804f8e1748b6d638a314102020dca5591cacae67fe420211d59369", size = 9428468, upload-time = "2025-10-16T18:18:40.984Z" }, + { url = "https://files.pythonhosted.org/packages/37/21/d376393ecaf26cb84aa475f46137a59ae6d50508acbf1a044d414d8f6d47/ty-0.0.1a23-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7516ee783ba3eba373fb82db8b989a14ed8620a45a9bb6e3a90571bc83b3e2a", size = 8880687, upload-time = "2025-10-16T18:18:43.34Z" }, + { url = "https://files.pythonhosted.org/packages/fd/f4/7cf58a02e0a8d062dd20d7816396587faba9ddfe4098ee88bb6ee3c272d4/ty-0.0.1a23-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6c8f9a861b51bbcf10f35d134a3c568a79a3acd3b0f2f1c004a2ccb00efdf7c1", size = 8281532, upload-time = "2025-10-16T18:18:45.806Z" }, + { url = "https://files.pythonhosted.org/packages/14/1b/ae616bbc4588b50ff1875588e734572a2b00102415e131bc20d794827865/ty-0.0.1a23-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d44a7ca68f4e79e7f06f23793397edfa28c2ac38e1330bf7100dce93015e412a", size = 8579585, upload-time = "2025-10-16T18:18:47.638Z" }, + { url = "https://files.pythonhosted.org/packages/b5/0c/3f4fc4721eb34abd7d86b43958b741b73727c9003f9977bacc3c91b3d7ca/ty-0.0.1a23-py3-none-musllinux_1_2_i686.whl", hash = "sha256:80a6818b22b25a27d5761a3cf377784f07d7a799f24b3ebcf9b4144b35b88871", size = 8675719, upload-time = "2025-10-16T18:18:49.536Z" }, + { url = "https://files.pythonhosted.org/packages/60/36/07d2c4e0230407419c10d3aa7c5035e023d9f70f07f4da2266fa0108109c/ty-0.0.1a23-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ef52c927ed6b5ebec290332ded02ce49ffdb3576683920b7013a7b2cd6bd5685", size = 8978349, upload-time = "2025-10-16T18:18:51.299Z" }, + { url = "https://files.pythonhosted.org/packages/7b/f9/abf666971434ea259a8d2006d2943eac0727a14aeccd24359341d377c2d1/ty-0.0.1a23-py3-none-win32.whl", hash = "sha256:0cc7500131a6a533d4000401026427cd538e33fda4e9004d7ad0db5a6f5500b1", size = 8279664, upload-time = "2025-10-16T18:18:53.132Z" }, + { url = "https://files.pythonhosted.org/packages/c6/3d/cb99e90adba6296f260ceaf3d02cc20563ec623b23a92ab94d17791cb537/ty-0.0.1a23-py3-none-win_amd64.whl", hash = "sha256:c89564e90dcc2f9564564d4a02cd703ed71cd9ccbb5a6a38ee49c44d86375f24", size = 8912398, upload-time = "2025-10-16T18:18:55.585Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/9fffb57f66317082fe3de4d08bb71557105c47676a114bdc9d52f6d3a910/ty-0.0.1a23-py3-none-win_arm64.whl", hash = "sha256:71aa203d6ae4de863a7f4626a8fe5f723beaa219988d176a6667f021b78a2af3", size = 8400343, upload-time = "2025-10-16T18:18:57.387Z" }, +] + +[[package]] +name = "typer" +version = "0.20.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8f/28/7c85c8032b91dbe79725b6f17d2fffc595dff06a35c7a30a37bef73a1ab4/typer-0.20.0.tar.gz", hash = "sha256:1aaf6494031793e4876fb0bacfa6a912b551cf43c1e63c800df8b1a866720c37", size = 106492, upload-time = "2025-10-20T17:03:49.445Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/64/7713ffe4b5983314e9d436a90d5bd4f63b6054e2aca783a3cfc44cb95bbf/typer-0.20.0-py3-none-any.whl", hash = "sha256:5b463df6793ec1dca6213a3cf4c0f03bc6e322ac5e16e13ddd622a889489784a", size = 47028, upload-time = "2025-10-20T17:03:47.617Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspect" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mypy-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/dc/74/1789779d91f1961fa9438e9a8710cdae6bd138c80d7303996933d117264a/typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78", size = 13825, upload-time = "2023-05-24T20:25:47.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/f3/107a22063bf27bdccf2024833d3445f4eea42b2e598abfbd46f6a63b6cb0/typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f", size = 8827, upload-time = "2023-05-24T20:25:45.287Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" }, +] + +[[package]] +name = "urllib3" +version = "2.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef4688ca63bdb2fdf113ca0a3be33f94488f2cadb690b0cf/uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d", size = 80605, upload-time = "2025-10-18T13:46:44.63Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" }, +] + +[[package]] +name = "web-ui" +version = "0.1.0" +source = { editable = "." } +dependencies = [ + { name = "browser-use" }, + { name = "gradio" }, + { name = "json-repair" }, + { name = "langchain-community" }, + { name = "langchain-ibm" }, + { name = "langchain-mcp-adapters" }, + { name = "langchain-mistralai" }, + { name = "langgraph" }, + { name = "maincontentextractor" }, + { name = "playwright" }, + { name = "pyperclip" }, + { name = "python-dotenv" }, +] + +[package.dev-dependencies] +dev = [ + { name = "pytest" }, + { name = "pytest-asyncio" }, + { name = "ruff" }, + { name = "ty" }, +] + +[package.metadata] +requires-dist = [ + { name = "browser-use", specifier = "==0.1.48" }, + { name = "gradio", specifier = ">=5.27.0" }, + { name = "json-repair", specifier = ">=0.25.0" }, + { name = "langchain-community", specifier = ">=0.3.0" }, + { name = "langchain-ibm", specifier = ">=0.3.10" }, + { name = "langchain-mcp-adapters", specifier = ">=0.0.9" }, + { name = "langchain-mistralai", specifier = ">=0.2.4" }, + { name = "langgraph", specifier = ">=0.3.34" }, + { name = "maincontentextractor", specifier = ">=0.0.4" }, + { name = "playwright", specifier = ">=1.40.0" }, + { name = "pyperclip", specifier = ">=1.9.0" }, + { name = "python-dotenv", specifier = ">=1.0.0" }, +] + +[package.metadata.requires-dev] +dev = [ + { name = "pytest", specifier = ">=8.0.0" }, + { name = "pytest-asyncio", specifier = ">=0.23.0" }, + { name = "ruff", specifier = ">=0.8.0" }, + { name = "ty", specifier = ">=0.0.1a23" }, +] + +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/d4/cc2f0400e9154df4b9964249da78ebd72f318e35ccc425e9f403c392f22a/xxhash-3.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b47bbd8cf2d72797f3c2772eaaac0ded3d3af26481a26d7d7d41dc2d3c46b04a", size = 32844, upload-time = "2025-10-02T14:34:14.037Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ec/1cc11cd13e26ea8bc3cb4af4eaadd8d46d5014aebb67be3f71fb0b68802a/xxhash-3.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2b6821e94346f96db75abaa6e255706fb06ebd530899ed76d32cd99f20dc52fa", size = 30809, upload-time = "2025-10-02T14:34:15.484Z" }, + { url = "https://files.pythonhosted.org/packages/04/5f/19fe357ea348d98ca22f456f75a30ac0916b51c753e1f8b2e0e6fb884cce/xxhash-3.6.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d0a9751f71a1a65ce3584e9cae4467651c7e70c9d31017fa57574583a4540248", size = 194665, upload-time = "2025-10-02T14:34:16.541Z" }, + { url = "https://files.pythonhosted.org/packages/90/3b/d1f1a8f5442a5fd8beedae110c5af7604dc37349a8e16519c13c19a9a2de/xxhash-3.6.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b29ee68625ab37b04c0b40c3fafdf24d2f75ccd778333cfb698f65f6c463f62", size = 213550, upload-time = "2025-10-02T14:34:17.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ef/3a9b05eb527457d5db13a135a2ae1a26c80fecd624d20f3e8dcc4cb170f3/xxhash-3.6.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6812c25fe0d6c36a46ccb002f40f27ac903bf18af9f6dd8f9669cb4d176ab18f", size = 212384, upload-time = "2025-10-02T14:34:19.182Z" }, + { url = "https://files.pythonhosted.org/packages/0f/18/ccc194ee698c6c623acbf0f8c2969811a8a4b6185af5e824cd27b9e4fd3e/xxhash-3.6.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4ccbff013972390b51a18ef1255ef5ac125c92dc9143b2d1909f59abc765540e", size = 445749, upload-time = "2025-10-02T14:34:20.659Z" }, + { url = "https://files.pythonhosted.org/packages/a5/86/cf2c0321dc3940a7aa73076f4fd677a0fb3e405cb297ead7d864fd90847e/xxhash-3.6.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:297b7fbf86c82c550e12e8fb71968b3f033d27b874276ba3624ea868c11165a8", size = 193880, upload-time = "2025-10-02T14:34:22.431Z" }, + { url = "https://files.pythonhosted.org/packages/82/fb/96213c8560e6f948a1ecc9a7613f8032b19ee45f747f4fca4eb31bb6d6ed/xxhash-3.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dea26ae1eb293db089798d3973a5fc928a18fdd97cc8801226fae705b02b14b0", size = 210912, upload-time = "2025-10-02T14:34:23.937Z" }, + { url = "https://files.pythonhosted.org/packages/40/aa/4395e669b0606a096d6788f40dbdf2b819d6773aa290c19e6e83cbfc312f/xxhash-3.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7a0b169aafb98f4284f73635a8e93f0735f9cbde17bd5ec332480484241aaa77", size = 198654, upload-time = "2025-10-02T14:34:25.644Z" }, + { url = "https://files.pythonhosted.org/packages/67/74/b044fcd6b3d89e9b1b665924d85d3f400636c23590226feb1eb09e1176ce/xxhash-3.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:08d45aef063a4531b785cd72de4887766d01dc8f362a515693df349fdb825e0c", size = 210867, upload-time = "2025-10-02T14:34:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/bc/fd/3ce73bf753b08cb19daee1eb14aa0d7fe331f8da9c02dd95316ddfe5275e/xxhash-3.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:929142361a48ee07f09121fe9e96a84950e8d4df3bb298ca5d88061969f34d7b", size = 414012, upload-time = "2025-10-02T14:34:28.409Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b3/5a4241309217c5c876f156b10778f3ab3af7ba7e3259e6d5f5c7d0129eb2/xxhash-3.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:51312c768403d8540487dbbfb557454cfc55589bbde6424456951f7fcd4facb3", size = 191409, upload-time = "2025-10-02T14:34:29.696Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/99bfbc15fb9abb9a72b088c1d95219fc4782b7d01fc835bd5744d66dd0b8/xxhash-3.6.0-cp311-cp311-win32.whl", hash = "sha256:d1927a69feddc24c987b337ce81ac15c4720955b667fe9b588e02254b80446fd", size = 30574, upload-time = "2025-10-02T14:34:31.028Z" }, + { url = "https://files.pythonhosted.org/packages/65/79/9d24d7f53819fe301b231044ea362ce64e86c74f6e8c8e51320de248b3e5/xxhash-3.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:26734cdc2d4ffe449b41d186bbeac416f704a482ed835d375a5c0cb02bc63fef", size = 31481, upload-time = "2025-10-02T14:34:32.062Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/15cd0e3e8772071344eab2961ce83f6e485111fed8beb491a3f1ce100270/xxhash-3.6.0-cp311-cp311-win_arm64.whl", hash = "sha256:d72f67ef8bf36e05f5b6c65e8524f265bd61071471cd4cf1d36743ebeeeb06b7", size = 27861, upload-time = "2025-10-02T14:34:33.555Z" }, + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/8aec23647a34a249f62e2398c42955acd9b4c6ed5cf08cbea94dc46f78d2/xxhash-3.6.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0f7b7e2ec26c1666ad5fc9dbfa426a6a3367ceaf79db5dd76264659d509d73b0", size = 30662, upload-time = "2025-10-02T14:37:01.743Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0b/b14510b38ba91caf43006209db846a696ceea6a847a0c9ba0a5b1adc53d6/xxhash-3.6.0-pp311-pypy311_pp73-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5dc1e14d14fa0f5789ec29a7062004b5933964bb9b02aae6622b8f530dc40296", size = 41056, upload-time = "2025-10-02T14:37:02.879Z" }, + { url = "https://files.pythonhosted.org/packages/50/55/15a7b8a56590e66ccd374bbfa3f9ffc45b810886c8c3b614e3f90bd2367c/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:881b47fc47e051b37d94d13e7455131054b56749b91b508b0907eb07900d1c13", size = 36251, upload-time = "2025-10-02T14:37:04.44Z" }, + { url = "https://files.pythonhosted.org/packages/62/b2/5ac99a041a29e58e95f907876b04f7067a0242cb85b5f39e726153981503/xxhash-3.6.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c6dc31591899f5e5666f04cc2e529e69b4072827085c1ef15294d91a004bc1bd", size = 32481, upload-time = "2025-10-02T14:37:05.869Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/8d95e906764a386a3d3b596f3c68bb63687dfca806373509f51ce8eea81f/xxhash-3.6.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:15e0dac10eb9309508bfc41f7f9deaa7755c69e35af835db9cb10751adebc35d", size = 31565, upload-time = "2025-10-02T14:37:06.966Z" }, +] + +[[package]] +name = "yarl" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "multidict" }, + { name = "propcache" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/ea/f3/d67de7260456ee105dc1d162d43a019ecad6b91e2f51809d6cddaa56690e/yarl-1.22.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8dee9c25c74997f6a750cd317b8ca63545169c098faee42c84aa5e506c819b53", size = 139980, upload-time = "2025-10-06T14:10:14.601Z" }, + { url = "https://files.pythonhosted.org/packages/01/88/04d98af0b47e0ef42597b9b28863b9060bb515524da0a65d5f4db160b2d5/yarl-1.22.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01e73b85a5434f89fc4fe27dcda2aff08ddf35e4d47bbbea3bdcd25321af538a", size = 93424, upload-time = "2025-10-06T14:10:16.115Z" }, + { url = "https://files.pythonhosted.org/packages/18/91/3274b215fd8442a03975ce6bee5fe6aa57a8326b29b9d3d56234a1dca244/yarl-1.22.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:22965c2af250d20c873cdbee8ff958fb809940aeb2e74ba5f20aaf6b7ac8c70c", size = 93821, upload-time = "2025-10-06T14:10:17.993Z" }, + { url = "https://files.pythonhosted.org/packages/61/3a/caf4e25036db0f2da4ca22a353dfeb3c9d3c95d2761ebe9b14df8fc16eb0/yarl-1.22.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4f15793aa49793ec8d1c708ab7f9eded1aa72edc5174cae703651555ed1b601", size = 373243, upload-time = "2025-10-06T14:10:19.44Z" }, + { url = "https://files.pythonhosted.org/packages/6e/9e/51a77ac7516e8e7803b06e01f74e78649c24ee1021eca3d6a739cb6ea49c/yarl-1.22.0-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5542339dcf2747135c5c85f68680353d5cb9ffd741c0f2e8d832d054d41f35a", size = 342361, upload-time = "2025-10-06T14:10:21.124Z" }, + { url = "https://files.pythonhosted.org/packages/d4/f8/33b92454789dde8407f156c00303e9a891f1f51a0330b0fad7c909f87692/yarl-1.22.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5c401e05ad47a75869c3ab3e35137f8468b846770587e70d71e11de797d113df", size = 387036, upload-time = "2025-10-06T14:10:22.902Z" }, + { url = "https://files.pythonhosted.org/packages/d9/9a/c5db84ea024f76838220280f732970aa4ee154015d7f5c1bfb60a267af6f/yarl-1.22.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:243dda95d901c733f5b59214d28b0120893d91777cb8aa043e6ef059d3cddfe2", size = 397671, upload-time = "2025-10-06T14:10:24.523Z" }, + { url = "https://files.pythonhosted.org/packages/11/c9/cd8538dc2e7727095e0c1d867bad1e40c98f37763e6d995c1939f5fdc7b1/yarl-1.22.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bec03d0d388060058f5d291a813f21c011041938a441c593374da6077fe21b1b", size = 377059, upload-time = "2025-10-06T14:10:26.406Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b9/ab437b261702ced75122ed78a876a6dec0a1b0f5e17a4ac7a9a2482d8abe/yarl-1.22.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0748275abb8c1e1e09301ee3cf90c8a99678a4e92e4373705f2a2570d581273", size = 365356, upload-time = "2025-10-06T14:10:28.461Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9d/8e1ae6d1d008a9567877b08f0ce4077a29974c04c062dabdb923ed98e6fe/yarl-1.22.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:47fdb18187e2a4e18fda2c25c05d8251a9e4a521edaed757fef033e7d8498d9a", size = 361331, upload-time = "2025-10-06T14:10:30.541Z" }, + { url = "https://files.pythonhosted.org/packages/ca/5a/09b7be3905962f145b73beb468cdd53db8aa171cf18c80400a54c5b82846/yarl-1.22.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c7044802eec4524fde550afc28edda0dd5784c4c45f0be151a2d3ba017daca7d", size = 382590, upload-time = "2025-10-06T14:10:33.352Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7f/59ec509abf90eda5048b0bc3e2d7b5099dffdb3e6b127019895ab9d5ef44/yarl-1.22.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:139718f35149ff544caba20fce6e8a2f71f1e39b92c700d8438a0b1d2a631a02", size = 385316, upload-time = "2025-10-06T14:10:35.034Z" }, + { url = "https://files.pythonhosted.org/packages/e5/84/891158426bc8036bfdfd862fabd0e0fa25df4176ec793e447f4b85cf1be4/yarl-1.22.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e1b51bebd221006d3d2f95fbe124b22b247136647ae5dcc8c7acafba66e5ee67", size = 374431, upload-time = "2025-10-06T14:10:37.76Z" }, + { url = "https://files.pythonhosted.org/packages/bb/49/03da1580665baa8bef5e8ed34c6df2c2aca0a2f28bf397ed238cc1bbc6f2/yarl-1.22.0-cp313-cp313-win32.whl", hash = "sha256:d3e32536234a95f513bd374e93d717cf6b2231a791758de6c509e3653f234c95", size = 81555, upload-time = "2025-10-06T14:10:39.649Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ee/450914ae11b419eadd067c6183ae08381cfdfcb9798b90b2b713bbebddda/yarl-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:47743b82b76d89a1d20b83e60d5c20314cbd5ba2befc9cda8f28300c4a08ed4d", size = 86965, upload-time = "2025-10-06T14:10:41.313Z" }, + { url = "https://files.pythonhosted.org/packages/98/4d/264a01eae03b6cf629ad69bae94e3b0e5344741e929073678e84bf7a3e3b/yarl-1.22.0-cp313-cp313-win_arm64.whl", hash = "sha256:5d0fcda9608875f7d052eff120c7a5da474a6796fe4d83e152e0e4d42f6d1a9b", size = 81205, upload-time = "2025-10-06T14:10:43.167Z" }, + { url = "https://files.pythonhosted.org/packages/88/fc/6908f062a2f77b5f9f6d69cecb1747260831ff206adcbc5b510aff88df91/yarl-1.22.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:719ae08b6972befcba4310e49edb1161a88cdd331e3a694b84466bd938a6ab10", size = 146209, upload-time = "2025-10-06T14:10:44.643Z" }, + { url = "https://files.pythonhosted.org/packages/65/47/76594ae8eab26210b4867be6f49129861ad33da1f1ebdf7051e98492bf62/yarl-1.22.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:47d8a5c446df1c4db9d21b49619ffdba90e77c89ec6e283f453856c74b50b9e3", size = 95966, upload-time = "2025-10-06T14:10:46.554Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ce/05e9828a49271ba6b5b038b15b3934e996980dd78abdfeb52a04cfb9467e/yarl-1.22.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cfebc0ac8333520d2d0423cbbe43ae43c8838862ddb898f5ca68565e395516e9", size = 97312, upload-time = "2025-10-06T14:10:48.007Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c5/7dffad5e4f2265b29c9d7ec869c369e4223166e4f9206fc2243ee9eea727/yarl-1.22.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4398557cbf484207df000309235979c79c4356518fd5c99158c7d38203c4da4f", size = 361967, upload-time = "2025-10-06T14:10:49.997Z" }, + { url = "https://files.pythonhosted.org/packages/50/b2/375b933c93a54bff7fc041e1a6ad2c0f6f733ffb0c6e642ce56ee3b39970/yarl-1.22.0-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2ca6fd72a8cd803be290d42f2dec5cdcd5299eeb93c2d929bf060ad9efaf5de0", size = 323949, upload-time = "2025-10-06T14:10:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/66/50/bfc2a29a1d78644c5a7220ce2f304f38248dc94124a326794e677634b6cf/yarl-1.22.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca1f59c4e1ab6e72f0a23c13fca5430f889634166be85dbf1013683e49e3278e", size = 361818, upload-time = "2025-10-06T14:10:54.078Z" }, + { url = "https://files.pythonhosted.org/packages/46/96/f3941a46af7d5d0f0498f86d71275696800ddcdd20426298e572b19b91ff/yarl-1.22.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c5010a52015e7c70f86eb967db0f37f3c8bd503a695a49f8d45700144667708", size = 372626, upload-time = "2025-10-06T14:10:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/c1/42/8b27c83bb875cd89448e42cd627e0fb971fa1675c9ec546393d18826cb50/yarl-1.22.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d7672ecf7557476642c88497c2f8d8542f8e36596e928e9bcba0e42e1e7d71f", size = 341129, upload-time = "2025-10-06T14:10:57.985Z" }, + { url = "https://files.pythonhosted.org/packages/49/36/99ca3122201b382a3cf7cc937b95235b0ac944f7e9f2d5331d50821ed352/yarl-1.22.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b7c88eeef021579d600e50363e0b6ee4f7f6f728cd3486b9d0f3ee7b946398d", size = 346776, upload-time = "2025-10-06T14:10:59.633Z" }, + { url = "https://files.pythonhosted.org/packages/85/b4/47328bf996acd01a4c16ef9dcd2f59c969f495073616586f78cd5f2efb99/yarl-1.22.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f4afb5c34f2c6fecdcc182dfcfc6af6cccf1aa923eed4d6a12e9d96904e1a0d8", size = 334879, upload-time = "2025-10-06T14:11:01.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ad/b77d7b3f14a4283bffb8e92c6026496f6de49751c2f97d4352242bba3990/yarl-1.22.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:59c189e3e99a59cf8d83cbb31d4db02d66cda5a1a4374e8a012b51255341abf5", size = 350996, upload-time = "2025-10-06T14:11:03.452Z" }, + { url = "https://files.pythonhosted.org/packages/81/c8/06e1d69295792ba54d556f06686cbd6a7ce39c22307100e3fb4a2c0b0a1d/yarl-1.22.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:5a3bf7f62a289fa90f1990422dc8dff5a458469ea71d1624585ec3a4c8d6960f", size = 356047, upload-time = "2025-10-06T14:11:05.115Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b8/4c0e9e9f597074b208d18cef227d83aac36184bfbc6eab204ea55783dbc5/yarl-1.22.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:de6b9a04c606978fdfe72666fa216ffcf2d1a9f6a381058d4378f8d7b1e5de62", size = 342947, upload-time = "2025-10-06T14:11:08.137Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/11f140a58bf4c6ad7aca69a892bff0ee638c31bea4206748fc0df4ebcb3a/yarl-1.22.0-cp313-cp313t-win32.whl", hash = "sha256:1834bb90991cc2999f10f97f5f01317f99b143284766d197e43cd5b45eb18d03", size = 86943, upload-time = "2025-10-06T14:11:10.284Z" }, + { url = "https://files.pythonhosted.org/packages/31/74/8b74bae38ed7fe6793d0c15a0c8207bbb819cf287788459e5ed230996cdd/yarl-1.22.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff86011bd159a9d2dfc89c34cfd8aff12875980e3bd6a39ff097887520e60249", size = 93715, upload-time = "2025-10-06T14:11:11.739Z" }, + { url = "https://files.pythonhosted.org/packages/69/66/991858aa4b5892d57aef7ee1ba6b4d01ec3b7eb3060795d34090a3ca3278/yarl-1.22.0-cp313-cp313t-win_arm64.whl", hash = "sha256:7861058d0582b847bc4e3a4a4c46828a410bca738673f35a29ba3ca5db0b473b", size = 83857, upload-time = "2025-10-06T14:11:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/46/b3/e20ef504049f1a1c54a814b4b9bed96d1ac0e0610c3b4da178f87209db05/yarl-1.22.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:34b36c2c57124530884d89d50ed2c1478697ad7473efd59cfd479945c95650e4", size = 140520, upload-time = "2025-10-06T14:11:15.465Z" }, + { url = "https://files.pythonhosted.org/packages/e4/04/3532d990fdbab02e5ede063676b5c4260e7f3abea2151099c2aa745acc4c/yarl-1.22.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0dd9a702591ca2e543631c2a017e4a547e38a5c0f29eece37d9097e04a7ac683", size = 93504, upload-time = "2025-10-06T14:11:17.106Z" }, + { url = "https://files.pythonhosted.org/packages/11/63/ff458113c5c2dac9a9719ac68ee7c947cb621432bcf28c9972b1c0e83938/yarl-1.22.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:594fcab1032e2d2cc3321bb2e51271e7cd2b516c7d9aee780ece81b07ff8244b", size = 94282, upload-time = "2025-10-06T14:11:19.064Z" }, + { url = "https://files.pythonhosted.org/packages/a7/bc/315a56aca762d44a6aaaf7ad253f04d996cb6b27bad34410f82d76ea8038/yarl-1.22.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d7a87a78d46a2e3d5b72587ac14b4c16952dd0887dbb051451eceac774411e", size = 372080, upload-time = "2025-10-06T14:11:20.996Z" }, + { url = "https://files.pythonhosted.org/packages/3f/3f/08e9b826ec2e099ea6e7c69a61272f4f6da62cb5b1b63590bb80ca2e4a40/yarl-1.22.0-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:852863707010316c973162e703bddabec35e8757e67fcb8ad58829de1ebc8590", size = 338696, upload-time = "2025-10-06T14:11:22.847Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9f/90360108e3b32bd76789088e99538febfea24a102380ae73827f62073543/yarl-1.22.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:131a085a53bfe839a477c0845acf21efc77457ba2bcf5899618136d64f3303a2", size = 387121, upload-time = "2025-10-06T14:11:24.889Z" }, + { url = "https://files.pythonhosted.org/packages/98/92/ab8d4657bd5b46a38094cfaea498f18bb70ce6b63508fd7e909bd1f93066/yarl-1.22.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:078a8aefd263f4d4f923a9677b942b445a2be970ca24548a8102689a3a8ab8da", size = 394080, upload-time = "2025-10-06T14:11:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/f5/e7/d8c5a7752fef68205296201f8ec2bf718f5c805a7a7e9880576c67600658/yarl-1.22.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bca03b91c323036913993ff5c738d0842fc9c60c4648e5c8d98331526df89784", size = 372661, upload-time = "2025-10-06T14:11:29.387Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2e/f4d26183c8db0bb82d491b072f3127fb8c381a6206a3a56332714b79b751/yarl-1.22.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:68986a61557d37bb90d3051a45b91fa3d5c516d177dfc6dd6f2f436a07ff2b6b", size = 364645, upload-time = "2025-10-06T14:11:31.423Z" }, + { url = "https://files.pythonhosted.org/packages/80/7c/428e5812e6b87cd00ee8e898328a62c95825bf37c7fa87f0b6bb2ad31304/yarl-1.22.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4792b262d585ff0dff6bcb787f8492e40698443ec982a3568c2096433660c694", size = 355361, upload-time = "2025-10-06T14:11:33.055Z" }, + { url = "https://files.pythonhosted.org/packages/ec/2a/249405fd26776f8b13c067378ef4d7dd49c9098d1b6457cdd152a99e96a9/yarl-1.22.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ebd4549b108d732dba1d4ace67614b9545b21ece30937a63a65dd34efa19732d", size = 381451, upload-time = "2025-10-06T14:11:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/67/a8/fb6b1adbe98cf1e2dd9fad71003d3a63a1bc22459c6e15f5714eb9323b93/yarl-1.22.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f87ac53513d22240c7d59203f25cc3beac1e574c6cd681bbfd321987b69f95fd", size = 383814, upload-time = "2025-10-06T14:11:37.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/f9/3aa2c0e480fb73e872ae2814c43bc1e734740bb0d54e8cb2a95925f98131/yarl-1.22.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:22b029f2881599e2f1b06f8f1db2ee63bd309e2293ba2d566e008ba12778b8da", size = 370799, upload-time = "2025-10-06T14:11:38.83Z" }, + { url = "https://files.pythonhosted.org/packages/50/3c/af9dba3b8b5eeb302f36f16f92791f3ea62e3f47763406abf6d5a4a3333b/yarl-1.22.0-cp314-cp314-win32.whl", hash = "sha256:6a635ea45ba4ea8238463b4f7d0e721bad669f80878b7bfd1f89266e2ae63da2", size = 82990, upload-time = "2025-10-06T14:11:40.624Z" }, + { url = "https://files.pythonhosted.org/packages/ac/30/ac3a0c5bdc1d6efd1b41fa24d4897a4329b3b1e98de9449679dd327af4f0/yarl-1.22.0-cp314-cp314-win_amd64.whl", hash = "sha256:0d6e6885777af0f110b0e5d7e5dda8b704efed3894da26220b7f3d887b839a79", size = 88292, upload-time = "2025-10-06T14:11:42.578Z" }, + { url = "https://files.pythonhosted.org/packages/df/0a/227ab4ff5b998a1b7410abc7b46c9b7a26b0ca9e86c34ba4b8d8bc7c63d5/yarl-1.22.0-cp314-cp314-win_arm64.whl", hash = "sha256:8218f4e98d3c10d683584cb40f0424f4b9fd6e95610232dd75e13743b070ee33", size = 82888, upload-time = "2025-10-06T14:11:44.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/5e/a15eb13db90abd87dfbefb9760c0f3f257ac42a5cac7e75dbc23bed97a9f/yarl-1.22.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:45c2842ff0e0d1b35a6bf1cd6c690939dacb617a70827f715232b2e0494d55d1", size = 146223, upload-time = "2025-10-06T14:11:46.796Z" }, + { url = "https://files.pythonhosted.org/packages/18/82/9665c61910d4d84f41a5bf6837597c89e665fa88aa4941080704645932a9/yarl-1.22.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:d947071e6ebcf2e2bee8fce76e10faca8f7a14808ca36a910263acaacef08eca", size = 95981, upload-time = "2025-10-06T14:11:48.845Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9a/2f65743589809af4d0a6d3aa749343c4b5f4c380cc24a8e94a3c6625a808/yarl-1.22.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:334b8721303e61b00019474cc103bdac3d7b1f65e91f0bfedeec2d56dfe74b53", size = 97303, upload-time = "2025-10-06T14:11:50.897Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ab/5b13d3e157505c43c3b43b5a776cbf7b24a02bc4cccc40314771197e3508/yarl-1.22.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e7ce67c34138a058fd092f67d07a72b8e31ff0c9236e751957465a24b28910c", size = 361820, upload-time = "2025-10-06T14:11:52.549Z" }, + { url = "https://files.pythonhosted.org/packages/fb/76/242a5ef4677615cf95330cfc1b4610e78184400699bdda0acb897ef5e49a/yarl-1.22.0-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d77e1b2c6d04711478cb1c4ab90db07f1609ccf06a287d5607fcd90dc9863acf", size = 323203, upload-time = "2025-10-06T14:11:54.225Z" }, + { url = "https://files.pythonhosted.org/packages/8c/96/475509110d3f0153b43d06164cf4195c64d16999e0c7e2d8a099adcd6907/yarl-1.22.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4647674b6150d2cae088fc07de2738a84b8bcedebef29802cf0b0a82ab6face", size = 363173, upload-time = "2025-10-06T14:11:56.069Z" }, + { url = "https://files.pythonhosted.org/packages/c9/66/59db471aecfbd559a1fd48aedd954435558cd98c7d0da8b03cc6c140a32c/yarl-1.22.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efb07073be061c8f79d03d04139a80ba33cbd390ca8f0297aae9cce6411e4c6b", size = 373562, upload-time = "2025-10-06T14:11:58.783Z" }, + { url = "https://files.pythonhosted.org/packages/03/1f/c5d94abc91557384719da10ff166b916107c1b45e4d0423a88457071dd88/yarl-1.22.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e51ac5435758ba97ad69617e13233da53908beccc6cfcd6c34bbed8dcbede486", size = 339828, upload-time = "2025-10-06T14:12:00.686Z" }, + { url = "https://files.pythonhosted.org/packages/5f/97/aa6a143d3afba17b6465733681c70cf175af89f76ec8d9286e08437a7454/yarl-1.22.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33e32a0dd0c8205efa8e83d04fc9f19313772b78522d1bdc7d9aed706bfd6138", size = 347551, upload-time = "2025-10-06T14:12:02.628Z" }, + { url = "https://files.pythonhosted.org/packages/43/3c/45a2b6d80195959239a7b2a8810506d4eea5487dce61c2a3393e7fc3c52e/yarl-1.22.0-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:bf4a21e58b9cde0e401e683ebd00f6ed30a06d14e93f7c8fd059f8b6e8f87b6a", size = 334512, upload-time = "2025-10-06T14:12:04.871Z" }, + { url = "https://files.pythonhosted.org/packages/86/a0/c2ab48d74599c7c84cb104ebd799c5813de252bea0f360ffc29d270c2caa/yarl-1.22.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:e4b582bab49ac33c8deb97e058cd67c2c50dac0dd134874106d9c774fd272529", size = 352400, upload-time = "2025-10-06T14:12:06.624Z" }, + { url = "https://files.pythonhosted.org/packages/32/75/f8919b2eafc929567d3d8411f72bdb1a2109c01caaab4ebfa5f8ffadc15b/yarl-1.22.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0b5bcc1a9c4839e7e30b7b30dd47fe5e7e44fb7054ec29b5bb8d526aa1041093", size = 357140, upload-time = "2025-10-06T14:12:08.362Z" }, + { url = "https://files.pythonhosted.org/packages/cf/72/6a85bba382f22cf78add705d8c3731748397d986e197e53ecc7835e76de7/yarl-1.22.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c0232bce2170103ec23c454e54a57008a9a72b5d1c3105dc2496750da8cfa47c", size = 341473, upload-time = "2025-10-06T14:12:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/35/18/55e6011f7c044dc80b98893060773cefcfdbf60dfefb8cb2f58b9bacbd83/yarl-1.22.0-cp314-cp314t-win32.whl", hash = "sha256:8009b3173bcd637be650922ac455946197d858b3630b6d8787aa9e5c4564533e", size = 89056, upload-time = "2025-10-06T14:12:13.317Z" }, + { url = "https://files.pythonhosted.org/packages/f9/86/0f0dccb6e59a9e7f122c5afd43568b1d31b8ab7dda5f1b01fb5c7025c9a9/yarl-1.22.0-cp314-cp314t-win_amd64.whl", hash = "sha256:9fb17ea16e972c63d25d4a97f016d235c78dd2344820eb35bc034bc32012ee27", size = 96292, upload-time = "2025-10-06T14:12:15.398Z" }, + { url = "https://files.pythonhosted.org/packages/48/b7/503c98092fb3b344a179579f55814b613c1fbb1c23b3ec14a7b008a66a6e/yarl-1.22.0-cp314-cp314t-win_arm64.whl", hash = "sha256:9f6d73c1436b934e3f01df1e1b21ff765cd1d28c77dfb9ace207f746d4610ee1", size = 85171, upload-time = "2025-10-06T14:12:16.935Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699, upload-time = "2024-07-15T00:14:04.909Z" }, + { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681, upload-time = "2024-07-15T00:14:13.99Z" }, + { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328, upload-time = "2024-07-15T00:14:16.588Z" }, + { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955, upload-time = "2024-07-15T00:14:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944, upload-time = "2024-07-15T00:14:22.173Z" }, + { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927, upload-time = "2024-07-15T00:14:24.825Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910, upload-time = "2024-07-15T00:14:26.982Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544, upload-time = "2024-07-15T00:14:29.582Z" }, + { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094, upload-time = "2024-07-15T00:14:40.126Z" }, + { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440, upload-time = "2024-07-15T00:14:42.786Z" }, + { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091, upload-time = "2024-07-15T00:14:45.184Z" }, + { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682, upload-time = "2024-07-15T00:14:47.407Z" }, + { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707, upload-time = "2024-07-15T00:15:03.529Z" }, + { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792, upload-time = "2024-07-15T00:15:28.372Z" }, + { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586, upload-time = "2024-07-15T00:15:32.26Z" }, + { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420, upload-time = "2024-07-15T00:15:34.004Z" }, + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, + { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975, upload-time = "2024-07-15T00:16:16.005Z" }, + { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448, upload-time = "2024-07-15T00:16:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269, upload-time = "2024-07-15T00:16:20.136Z" }, + { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228, upload-time = "2024-07-15T00:16:23.398Z" }, + { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891, upload-time = "2024-07-15T00:16:26.391Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310, upload-time = "2024-07-15T00:16:29.018Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912, upload-time = "2024-07-15T00:16:31.871Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946, upload-time = "2024-07-15T00:16:34.593Z" }, + { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994, upload-time = "2024-07-15T00:16:36.887Z" }, + { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681, upload-time = "2024-07-15T00:16:39.709Z" }, + { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239, upload-time = "2024-07-15T00:16:41.83Z" }, + { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149, upload-time = "2024-07-15T00:16:44.287Z" }, + { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392, upload-time = "2024-07-15T00:16:46.423Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299, upload-time = "2024-07-15T00:16:49.053Z" }, + { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862, upload-time = "2024-07-15T00:16:51.003Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578, upload-time = "2024-07-15T00:16:53.135Z" }, +] diff --git a/webui.py b/webui.py index eef1e3cb..e26dfb9d 100644 --- a/webui.py +++ b/webui.py @@ -1,467 +1,168 @@ -# -*- coding: utf-8 -*- -# @Time : 2025/1/1 -# @Author : wenshao -# @Email : wenshaoguo1026@gmail.com -# @Project : browser-use-webui -# @FileName: webui.py -import pdb - -from dotenv import load_dotenv - -load_dotenv() import argparse +import logging +import signal +import socket +import sys +from contextlib import closing -import asyncio +from dotenv import load_dotenv -import gradio as gr -import asyncio -import os -from pprint import pprint -from typing import List, Dict, Any +from src.web_ui.webui.interface import create_ui, theme_map -from playwright.async_api import async_playwright -from browser_use.browser.browser import Browser, BrowserConfig -from browser_use.browser.context import ( - BrowserContext, - BrowserContextConfig, - BrowserContextWindowSize, -) -from browser_use.agent.service import Agent +load_dotenv() -from src.browser.custom_browser import CustomBrowser, BrowserConfig -from src.browser.custom_context import BrowserContext, BrowserContextConfig -from src.controller.custom_controller import CustomController -from src.agent.custom_agent import CustomAgent -from src.agent.custom_prompts import CustomSystemPrompt +logger = logging.getLogger(__name__) -from src.utils import utils -async def run_browser_agent( - agent_type, - llm_provider, - llm_model_name, - llm_temperature, - llm_base_url, - llm_api_key, - use_own_browser, - headless, - disable_security, - window_w, - window_h, - save_recording_path, - task, - add_infos, - max_steps, - use_vision -): - # Ensure the recording directory exists - os.makedirs(save_recording_path, exist_ok=True) +def is_port_available(host: str, port: int) -> bool: + """Check if a port is available on the given host.""" + try: + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: + sock.settimeout(1) + result = sock.connect_ex((host, port)) + return result != 0 # Port is available if connection failed + except Exception: + return False + + +def find_available_port(host: str, start_port: int, max_attempts: int = 10) -> int: + """Find an available port starting from start_port.""" + for port in range(start_port, start_port + max_attempts): + if is_port_available(host, port): + return port + raise OSError( + f"Could not find an available port in range {start_port}-{start_port + max_attempts - 1}" + ) - # Get the list of existing videos before the agent runs - existing_videos = set(glob.glob(os.path.join(save_recording_path, '*.[mM][pP]4')) + - glob.glob(os.path.join(save_recording_path, '*.[wW][eE][bB][mM]'))) - # Run the agent - llm = utils.get_llm_model( - provider=llm_provider, - model_name=llm_model_name, - temperature=llm_temperature, - base_url=llm_base_url, - api_key=llm_api_key - ) - if agent_type == "org": - final_result, errors, model_actions, model_thoughts = await run_org_agent( - llm=llm, - headless=headless, - disable_security=disable_security, - window_w=window_w, - window_h=window_h, - save_recording_path=save_recording_path, - task=task, - max_steps=max_steps, - use_vision=use_vision - ) - elif agent_type == "custom": - final_result, errors, model_actions, model_thoughts = await run_custom_agent( - llm=llm, - use_own_browser=use_own_browser, - headless=headless, - disable_security=disable_security, - window_w=window_w, - window_h=window_h, - save_recording_path=save_recording_path, - task=task, - add_infos=add_infos, - max_steps=max_steps, - use_vision=use_vision - ) - else: - raise ValueError(f"Invalid agent type: {agent_type}") +def setup_signal_handlers(demo): + """Setup graceful shutdown handlers.""" - # Get the list of videos after the agent runs - new_videos = set(glob.glob(os.path.join(save_recording_path, '*.[mM][pP]4')) + - glob.glob(os.path.join(save_recording_path, '*.[wW][eE][bB][mM]'))) + def signal_handler(sig, frame): + print("\n🛑 Shutting down gracefully...") + try: + demo.close() + except Exception as e: + logger.error(f"Error during shutdown: {e}") + sys.exit(0) - # Find the newly created video - latest_video = None - if new_videos - existing_videos: - latest_video = list(new_videos - existing_videos)[0] # Get the first new video + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) - return final_result, errors, model_actions, model_thoughts, latest_video -async def run_org_agent( - llm, - headless, - disable_security, - window_w, - window_h, - save_recording_path, - task, - max_steps, - use_vision -): - browser = Browser( - config=BrowserConfig( - headless=headless, - disable_security=disable_security, - extra_chromium_args=[f'--window-size={window_w},{window_h}'], - ) +def main(): + parser = argparse.ArgumentParser( + description="Browser Use WebUI - AI-Powered Browser Automation", + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +Examples: + python webui.py # Start with defaults (127.0.0.1:7788) + python webui.py --port 8080 # Use custom port + python webui.py --ip 0.0.0.0 # Expose to network + python webui.py --theme Soft # Use different theme + python webui.py --auto-port # Auto-find available port + """, ) - async with await browser.new_context( - config=BrowserContextConfig( - trace_path='./tmp/traces', - save_recording_path=save_recording_path if save_recording_path else None, - no_viewport=False, - browser_window_size=BrowserContextWindowSize(width=window_w, height=window_h), - ) - ) as browser_context: - agent = Agent( - task=task, - llm=llm, - use_vision=use_vision, - browser_context=browser_context, - ) - history = await agent.run(max_steps=max_steps) + parser.add_argument( + "--ip", type=str, default="127.0.0.1", help="IP address to bind to (default: 127.0.0.1)" + ) + parser.add_argument("--port", type=int, default=7788, help="Port to listen on (default: 7788)") + parser.add_argument( + "--theme", + type=str, + default="Ocean", + choices=theme_map.keys(), + help="Theme to use for the UI (default: Ocean)", + ) + parser.add_argument( + "--auto-port", + action="store_true", + help="Automatically find an available port if specified port is in use", + ) + parser.add_argument("--share", action="store_true", help="Create a public Gradio share link") + parser.add_argument( + "--debug", action="store_true", help="Enable debug mode with detailed logging" + ) + args = parser.parse_args() - final_result = history.final_result() - errors = history.errors() - model_actions = history.model_actions() - model_thoughts = history.model_thoughts() - await browser.close() - return final_result, errors, model_actions, model_thoughts + # Configure logging + log_level = logging.DEBUG if args.debug else logging.INFO + logging.basicConfig( + level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" + ) -async def run_custom_agent( - llm, - use_own_browser, - headless, - disable_security, - window_w, - window_h, - save_recording_path, - task, - add_infos, - max_steps, - use_vision -): - controller = CustomController() - playwright = None - browser_context_ = None - try: - if use_own_browser: - playwright = await async_playwright().start() - chrome_exe = os.getenv("CHROME_PATH", "") - chrome_use_data = os.getenv("CHROME_USER_DATA", "") - browser_context_ = await playwright.chromium.launch_persistent_context( - user_data_dir=chrome_use_data, - executable_path=chrome_exe, - no_viewport=False, - headless=headless, # 保持浏览器窗口可见 - user_agent=( - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ' - '(KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36' - ), - java_script_enabled=True, - bypass_csp=disable_security, - ignore_https_errors=disable_security, - record_video_dir=save_recording_path if save_recording_path else None, - record_video_size={'width': window_w, 'height': window_h} - ) + print("\n" + "=" * 70) + print("🌐 Browser Use WebUI - AI-Powered Browser Automation") + print("=" * 70) + + # Check if port is available + selected_port = args.port + if not is_port_available(args.ip, selected_port): + if args.auto_port: + print(f"⚠️ Port {selected_port} is already in use, finding alternative...") + try: + selected_port = find_available_port(args.ip, selected_port + 1) + print(f"✅ Found available port: {selected_port}") + except OSError as e: + print(f"❌ Error: {e}") + print("\n💡 Try one of these:") + print(f" - Stop the process using port {args.port}") + print(" - Use a different port: python webui.py --port 8080") + print(" - Use --auto-port flag to find available port automatically") + sys.exit(1) else: - browser_context_ = None + print(f"❌ Error: Port {selected_port} is already in use!") + print("\n💡 Try one of these:") + print(f" 1. Stop the existing process on port {selected_port}") + print(" 2. Use a different port: python webui.py --port 8080") + print(" 3. Use auto-port selection: python webui.py --auto-port") + sys.exit(1) - browser = CustomBrowser( - config=BrowserConfig( - headless=headless, - disable_security=disable_security, - extra_chromium_args=[f'--window-size={window_w},{window_h}'], - ) + try: + print("\n🚀 Starting server...") + print(f" • Theme: {args.theme}") + print(f" • Host: {args.ip}") + print(f" • Port: {selected_port}") + if args.share: + print(" • Share: Enabled (public link will be generated)") + + # Create and launch the UI + demo = create_ui(theme_name=args.theme) + + # Setup graceful shutdown + setup_signal_handlers(demo) + + print("\n" + "=" * 70) + print(f"✅ Server running at: http://{args.ip}:{selected_port}") + if args.ip == "127.0.0.1": + print(f" Local access: http://localhost:{selected_port}") + print("=" * 70) + print("\n💡 Quick Tips:") + print(" • Press Ctrl+C to stop the server") + print(" • Press '?' in the UI to see keyboard shortcuts") + print(" • Check the Quick Start tab for preset configurations") + print("\n📚 Documentation: https://github.com/savagelysubtle/web-ui-1") + print("-" * 70 + "\n") + + # Launch with error handling + demo.queue().launch( + server_name=args.ip, + server_port=selected_port, + share=args.share, + show_error=True, + quiet=False, ) - async with await browser.new_context( - config=BrowserContextConfig( - trace_path='./tmp/result_processing', - save_recording_path=save_recording_path if save_recording_path else None, - no_viewport=False, - browser_window_size=BrowserContextWindowSize(width=window_w, height=window_h), - ), - context=browser_context_ - ) as browser_context: - agent = CustomAgent( - task=task, - add_infos=add_infos, - use_vision=use_vision, - llm=llm, - browser_context=browser_context, - controller=controller, - system_prompt_class=CustomSystemPrompt - ) - history = await agent.run(max_steps=max_steps) - - final_result = history.final_result() - errors = history.errors() - model_actions = history.model_actions() - model_thoughts = history.model_thoughts() + except KeyboardInterrupt: + print("\n🛑 Shutting down gracefully...") + sys.exit(0) except Exception as e: - import traceback - traceback.print_exc() - final_result = "" - errors = str(e) + "\n" + traceback.format_exc() - model_actions = "" - model_thoughts = "" - finally: - # 显式关闭持久化上下文 - if browser_context_: - await browser_context_.close() - - # 关闭 Playwright 对象 - if playwright: - await playwright.stop() - await browser.close() - return final_result, errors, model_actions, model_thoughts - - -import argparse -import gradio as gr -from gradio.themes import Base, Default, Soft, Monochrome, Glass, Origin, Citrus, Ocean -import os, glob - -# Define the theme map globally -theme_map = { - "Default": Default(), - "Soft": Soft(), - "Monochrome": Monochrome(), - "Glass": Glass(), - "Origin": Origin(), - "Citrus": Citrus(), - "Ocean": Ocean() -} - -def create_ui(theme_name="Ocean"): - css = """ - .gradio-container { - max-width: 1200px !important; - margin: auto !important; - padding-top: 20px !important; - } - .header-text { - text-align: center; - margin-bottom: 30px; - } - .theme-section { - margin-bottom: 20px; - padding: 15px; - border-radius: 10px; - } - """ - - js = """ - function refresh() { - const url = new URL(window.location); - if (url.searchParams.get('__theme') !== 'dark') { - url.searchParams.set('__theme', 'dark'); - window.location.href = url.href; - } - } - """ - - with gr.Blocks(title="Browser Use WebUI", theme=theme_map[theme_name], css=css, js=js) as demo: - with gr.Row(): - gr.Markdown( - """ - # 🌐 Browser Use WebUI - ### Control your browser with AI assistance - """, - elem_classes=["header-text"] - ) - - with gr.Tabs() as tabs: - with gr.TabItem("🤖 Agent Settings", id=1): - with gr.Group(): - agent_type = gr.Radio( - ["org", "custom"], - label="Agent Type", - value="custom", - info="Select the type of agent to use" - ) - max_steps = gr.Slider( - minimum=1, - maximum=200, - value=100, - step=1, - label="Max Run Steps", - info="Maximum number of steps the agent will take" - ) - use_vision = gr.Checkbox( - label="Use Vision", - value=True, - info="Enable visual processing capabilities" - ) - - with gr.TabItem("🔧 LLM Configuration", id=2): - with gr.Group(): - llm_provider = gr.Dropdown( - ["anthropic", "openai", "gemini", "azure_openai", "deepseek", "ollama"], - label="LLM Provider", - value="gemini", - info="Select your preferred language model provider" - ) - llm_model_name = gr.Textbox( - label="Model Name", - value="gemini-2.0-flash-exp", - info="Specify the model to use" - ) - llm_temperature = gr.Slider( - minimum=0.0, - maximum=2.0, - value=1.0, - step=0.1, - label="Temperature", - info="Controls randomness in model outputs" - ) - with gr.Row(): - llm_base_url = gr.Textbox( - label="Base URL", - info="API endpoint URL (if required)" - ) - llm_api_key = gr.Textbox( - label="API Key", - type="password", - info="Your API key" - ) - - with gr.TabItem("🌐 Browser Settings", id=3): - with gr.Group(): - with gr.Row(): - use_own_browser = gr.Checkbox( - label="Use Own Browser", - value=False, - info="Use your existing browser instance" - ) - headless = gr.Checkbox( - label="Headless Mode", - value=False, - info="Run browser without GUI" - ) - disable_security = gr.Checkbox( - label="Disable Security", - value=True, - info="Disable browser security features" - ) - - with gr.Row(): - window_w = gr.Number( - label="Window Width", - value=1920, - info="Browser window width" - ) - window_h = gr.Number( - label="Window Height", - value=1080, - info="Browser window height" - ) - - save_recording_path = gr.Textbox( - label="Recording Path", - placeholder="e.g. ./tmp/record_videos", - value="./tmp/record_videos", - info="Path to save browser recordings" - ) - - with gr.TabItem("📝 Task Settings", id=4): - task = gr.Textbox( - label="Task Description", - lines=4, - placeholder="Enter your task here...", - value="go to google.com and type 'OpenAI' click search and give me the first url", - info="Describe what you want the agent to do" - ) - add_infos = gr.Textbox( - label="Additional Information", - lines=3, - placeholder="Add any helpful context or instructions...", - info="Optional hints to help the LLM complete the task" - ) - - with gr.Row(): - run_button = gr.Button("▶️ Run Agent", variant="primary", scale=2) - stop_button = gr.Button("⏹️ Stop", variant="stop", scale=1) - - with gr.TabItem("🎬 Recordings", id=5): - recording_display = gr.Video(label="Latest Recording") - - with gr.Group(): - gr.Markdown("### Results") - with gr.Row(): - with gr.Column(): - final_result_output = gr.Textbox( - label="Final Result", - lines=3, - show_label=True - ) - with gr.Column(): - errors_output = gr.Textbox( - label="Errors", - lines=3, - show_label=True - ) - with gr.Row(): - with gr.Column(): - model_actions_output = gr.Textbox( - label="Model Actions", - lines=3, - show_label=True - ) - with gr.Column(): - model_thoughts_output = gr.Textbox( - label="Model Thoughts", - lines=3, - show_label=True - ) - - # Run button click handler - run_button.click( - fn=run_browser_agent, - inputs=[ - agent_type, llm_provider, llm_model_name, llm_temperature, - llm_base_url, llm_api_key, use_own_browser, headless, - disable_security, window_w, window_h, save_recording_path, - task, add_infos, max_steps, use_vision - ], - outputs=[final_result_output, errors_output, model_actions_output, model_thoughts_output, recording_display] - ) - - return demo - -def main(): - parser = argparse.ArgumentParser(description="Gradio UI for Browser Agent") - parser.add_argument("--ip", type=str, default="127.0.0.1", help="IP address to bind to") - parser.add_argument("--port", type=int, default=7788, help="Port to listen on") - parser.add_argument("--theme", type=str, default="Ocean", choices=theme_map.keys(), help="Theme to use for the UI") - parser.add_argument("--dark-mode", action="store_true", help="Enable dark mode") - args = parser.parse_args() + logger.error(f"Failed to start server: {e}", exc_info=args.debug) + print(f"\n❌ Error starting server: {e}") + if not args.debug: + print("💡 Run with --debug flag for detailed error information") + sys.exit(1) - demo = create_ui(theme_name=args.theme) - demo.launch(server_name=args.ip, server_port=args.port) -if __name__ == '__main__': +if __name__ == "__main__": main()