From 3d9ff65aaf9163f4e161473d617f49f4970269a6 Mon Sep 17 00:00:00 2001 From: choej2303 Date: Sat, 13 Dec 2025 14:49:04 +0900 Subject: [PATCH 1/6] feat: Migrate Python server to MCPForUnity package structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Move Server/ directory to MCPForUnity/Server~/ for Unity package integration - Implement FastMCP server with logging, telemetry, and Unity connection management - Add comprehensive tool and resource registry system - Include Python wrapper for Windows line ending fixes - Update CI workflows to reflect new server location - Add tool auto-discovery and Unity context helpers - Implement transport layer with plugin hub and middleware Python 서버를 MCPForUnity 패키지 구조로 마이그레이션 - Unity 패키지 통합을 위해 Server/ 디렉토리를 MCPForUnity/Server~/로 이동 - 로깅, 텔레메트리, Unity 연결 관리를 포함한 FastMCP 서버 구현 - 포괄적인 도구 및 리소스 레지스트리 시스템 추가 - Windows 줄 끝 수정을 위한 Python wrapper 포함 - 새 서버 위치를 반영하도록 CI 워크플로우 업데이트 - 도구 자동 검색 및 Unity 컨텍스트 헬퍼 추가 - 플러그인 허브 및 미들웨어를 사용한 전송 계층 구현 --- .github/workflows/bump-version.yml | 10 +- .github/workflows/claude-mcp-preflight.yml | 12 +- .github/workflows/claude-nl-suite.yml | 24 +- .github/workflows/python-tests.yml | 10 +- {Server => MCPForUnity/Server~}/Dockerfile | 0 {Server => MCPForUnity/Server~}/README.md | 0 {Server => MCPForUnity/Server~}/__init__.py | 0 MCPForUnity/Server~/mcp_wrapper.py | 121 ++ MCPForUnity/Server~/pyproject.toml | 27 + .../Server~}/pyrightconfig.json | 0 .../Server~}/src/__init__.py | 0 .../Server~}/src/core/__init__.py | 0 .../Server~}/src/core/config.py | 0 .../Server~}/src/core/logging_decorator.py | 0 .../Server~}/src/core/telemetry.py | 6 +- .../Server~}/src/core/telemetry_decorator.py | 0 {Server => MCPForUnity/Server~}/src/main.py | 15 +- .../Server~}/src/models/__init__.py | 0 .../Server~}/src/models/models.py | 0 .../Server~}/src/models/unity_response.py | 0 .../Server~}/src/routes/__init__.py | 0 .../Server~}/src/services/__init__.py | 0 .../src/services/custom_tool_service.py | 0 .../src/services/registry/__init__.py | 0 .../services/registry/resource_registry.py | 0 .../src/services/registry/tool_registry.py | 0 .../src/services/resources/__init__.py | 0 .../src/services/resources/active_tool.py | 0 .../src/services/resources/custom_tools.py | 0 .../src/services/resources/editor_state.py | 0 .../Server~}/src/services/resources/layers.py | 0 .../src/services/resources/menu_items.py | 0 .../src/services/resources/prefab_stage.py | 0 .../src/services/resources/project_info.py | 0 .../src/services/resources/selection.py | 0 .../Server~}/src/services/resources/tags.py | 0 .../Server~}/src/services/resources/tests.py | 0 .../src/services/resources/unity_instances.py | 0 .../src/services/resources/windows.py | 0 .../Server~}/src/services/tools/__init__.py | 8 +- .../src/services/tools/batch_execute.py | 0 .../services/tools/debug_request_context.py | 0 .../src/services/tools/execute_custom_tool.py | 0 .../src/services/tools/execute_menu_item.py | 0 .../src/services/tools/find_in_file.py | 0 .../src/services/tools/manage_asset.py | 0 .../src/services/tools/manage_editor.py | 0 .../src/services/tools/manage_gameobject.py | 0 .../src/services/tools/manage_material.py | 0 .../src/services/tools/manage_prefabs.py | 0 .../src/services/tools/manage_scene.py | 0 .../src/services/tools/manage_script.py | 0 .../src/services/tools/manage_shader.py | 0 .../src/services/tools/read_console.py | 0 .../Server~}/src/services/tools/run_tests.py | 0 .../src/services/tools/script_apply_edits.py | 0 .../src/services/tools/set_active_instance.py | 0 .../Server~}/src/services/tools/utils.py | 0 .../Server~}/src/transport/__init__.py | 0 .../src/transport/legacy/port_discovery.py | 0 .../transport/legacy/stdio_port_registry.py | 0 .../src/transport/legacy/unity_connection.py | 0 .../Server~}/src/transport/models.py | 0 .../Server~}/src/transport/plugin_hub.py | 0 .../Server~}/src/transport/plugin_registry.py | 0 .../transport/unity_instance_middleware.py | 0 .../Server~}/src/transport/unity_transport.py | 0 .../Server~}/src/utils/module_discovery.py | 0 .../Server~}/src/utils/reload_sentinel.py | 0 .../Server~}/tests/__init__.py | 0 .../Server~}/tests/integration/__init__.py | 0 .../Server~}/tests/integration/conftest.py | 0 .../test_domain_reload_resilience.py | 0 .../test_edit_normalization_and_noop.py | 0 .../test_edit_strict_and_warnings.py | 0 .../integration/test_find_in_file_minimal.py | 0 .../tests/integration/test_get_sha.py | 0 .../tests/integration/test_helpers.py | 0 .../test_improved_anchor_matching.py | 0 .../test_instance_routing_comprehensive.py | 0 .../test_instance_targeting_resolution.py | 0 .../integration/test_json_parsing_simple.py | 0 .../tests/integration/test_logging_stdout.py | 0 .../test_manage_asset_json_parsing.py | 0 .../test_manage_asset_param_coercion.py | 0 .../test_manage_gameobject_param_coercion.py | 0 .../integration/test_manage_script_uri.py | 0 .../integration/test_read_console_truncate.py | 0 .../integration/test_read_resource_minimal.py | 0 .../tests/integration/test_resources_api.py | 0 .../tests/integration/test_script_editing.py | 0 .../tests/integration/test_script_tools.py | 0 .../test_telemetry_endpoint_validation.py | 0 .../test_telemetry_queue_worker.py | 0 .../integration/test_telemetry_subaction.py | 0 .../integration/test_transport_framing.py | 0 .../test_validate_script_summary.py | 0 .../Server~}/tests/pytest.ini | 0 {Server => MCPForUnity/Server~}/uv.lock | 1069 ++++++++++------- MCPForUnity/Server~/wrapper.js | 112 ++ Server/pyproject.toml | 28 - 101 files changed, 916 insertions(+), 526 deletions(-) rename {Server => MCPForUnity/Server~}/Dockerfile (100%) rename {Server => MCPForUnity/Server~}/README.md (100%) rename {Server => MCPForUnity/Server~}/__init__.py (100%) create mode 100644 MCPForUnity/Server~/mcp_wrapper.py create mode 100644 MCPForUnity/Server~/pyproject.toml rename {Server => MCPForUnity/Server~}/pyrightconfig.json (100%) rename {Server => MCPForUnity/Server~}/src/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/core/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/core/config.py (100%) rename {Server => MCPForUnity/Server~}/src/core/logging_decorator.py (100%) rename {Server => MCPForUnity/Server~}/src/core/telemetry.py (99%) rename {Server => MCPForUnity/Server~}/src/core/telemetry_decorator.py (100%) rename {Server => MCPForUnity/Server~}/src/main.py (98%) rename {Server => MCPForUnity/Server~}/src/models/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/models/models.py (100%) rename {Server => MCPForUnity/Server~}/src/models/unity_response.py (100%) rename {Server => MCPForUnity/Server~}/src/routes/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/services/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/services/custom_tool_service.py (100%) rename {Server => MCPForUnity/Server~}/src/services/registry/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/services/registry/resource_registry.py (100%) rename {Server => MCPForUnity/Server~}/src/services/registry/tool_registry.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/active_tool.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/custom_tools.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/editor_state.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/layers.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/menu_items.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/prefab_stage.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/project_info.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/selection.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/tags.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/tests.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/unity_instances.py (100%) rename {Server => MCPForUnity/Server~}/src/services/resources/windows.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/__init__.py (90%) rename {Server => MCPForUnity/Server~}/src/services/tools/batch_execute.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/debug_request_context.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/execute_custom_tool.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/execute_menu_item.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/find_in_file.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_asset.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_editor.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_gameobject.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_material.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_prefabs.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_scene.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_script.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/manage_shader.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/read_console.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/run_tests.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/script_apply_edits.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/set_active_instance.py (100%) rename {Server => MCPForUnity/Server~}/src/services/tools/utils.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/__init__.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/legacy/port_discovery.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/legacy/stdio_port_registry.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/legacy/unity_connection.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/models.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/plugin_hub.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/plugin_registry.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/unity_instance_middleware.py (100%) rename {Server => MCPForUnity/Server~}/src/transport/unity_transport.py (100%) rename {Server => MCPForUnity/Server~}/src/utils/module_discovery.py (100%) rename {Server => MCPForUnity/Server~}/src/utils/reload_sentinel.py (100%) rename {Server => MCPForUnity/Server~}/tests/__init__.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/__init__.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/conftest.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_domain_reload_resilience.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_edit_normalization_and_noop.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_edit_strict_and_warnings.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_find_in_file_minimal.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_get_sha.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_helpers.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_improved_anchor_matching.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_instance_routing_comprehensive.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_instance_targeting_resolution.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_json_parsing_simple.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_logging_stdout.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_manage_asset_json_parsing.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_manage_asset_param_coercion.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_manage_gameobject_param_coercion.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_manage_script_uri.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_read_console_truncate.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_read_resource_minimal.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_resources_api.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_script_editing.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_script_tools.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_telemetry_endpoint_validation.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_telemetry_queue_worker.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_telemetry_subaction.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_transport_framing.py (100%) rename {Server => MCPForUnity/Server~}/tests/integration/test_validate_script_summary.py (100%) rename {Server => MCPForUnity/Server~}/tests/pytest.ini (100%) rename {Server => MCPForUnity/Server~}/uv.lock (50%) create mode 100644 MCPForUnity/Server~/wrapper.js delete mode 100644 Server/pyproject.toml diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml index cd810c449..9c0ffe41d 100644 --- a/.github/workflows/bump-version.yml +++ b/.github/workflows/bump-version.yml @@ -67,11 +67,11 @@ jobs: jq ".version = \"${NEW_VERSION}\"" MCPForUnity/package.json > MCPForUnity/package.json.tmp mv MCPForUnity/package.json.tmp MCPForUnity/package.json - echo "Updating Server/pyproject.toml to $NEW_VERSION" - sed -i '0,/^version = ".*"/s//version = "'"$NEW_VERSION"'"/' "Server/pyproject.toml" + echo "Updating MCPForUnity/Server~/pyproject.toml to $NEW_VERSION" + sed -i '0,/^version = ".*"/s//version = "'"$NEW_VERSION"'"/' "MCPForUnity/Server~/pyproject.toml" - echo "Updating Server/README.md version references to v$NEW_VERSION" - sed -i 's|git+https://github.com/CoplayDev/unity-mcp@v[0-9]\+\.[0-9]\+\.[0-9]\+#subdirectory=Server|git+https://github.com/CoplayDev/unity-mcp@v'"$NEW_VERSION"'#subdirectory=Server|g' Server/README.md + echo "Updating MCPForUnity/Server~/README.md version references to v$NEW_VERSION" + sed -i 's|git+https://github.com/CoplayDev/unity-mcp@v[0-9]\+\.[0-9]\+\.[0-9]\+#subdirectory=MCPForUnity/Server~|git+https://github.com/CoplayDev/unity-mcp@v'"$NEW_VERSION"'#subdirectory=MCPForUnity/Server~|g' "MCPForUnity/Server~/README.md" - name: Commit and push changes env: @@ -81,7 +81,7 @@ jobs: set -euo pipefail git config user.name "GitHub Actions" git config user.email "actions@github.com" - git add MCPForUnity/package.json "Server/pyproject.toml" Server/README.md + git add MCPForUnity/package.json "MCPForUnity/Server~/pyproject.toml" "MCPForUnity/Server~/README.md" if git diff --cached --quiet; then echo "No version changes to commit." else diff --git a/.github/workflows/claude-mcp-preflight.yml b/.github/workflows/claude-mcp-preflight.yml index dff69bd7b..c018397a2 100644 --- a/.github/workflows/claude-mcp-preflight.yml +++ b/.github/workflows/claude-mcp-preflight.yml @@ -25,10 +25,10 @@ jobs: uv venv echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV" echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" - if [ -f Server/pyproject.toml ]; then - uv pip install -e Server - elif [ -f Server/requirements.txt ]; then - uv pip install -r Server/requirements.txt + if [ -f MCPForUnity/Server~/pyproject.toml ]; then + uv pip install -e "MCPForUnity/Server~" + elif [ -f MCPForUnity/Server~/requirements.txt ]; then + uv pip install -r MCPForUnity/Server~/requirements.txt else echo "No MCP Python deps found" >&2 exit 1 @@ -48,8 +48,6 @@ jobs: cat > "$UNITY_MCP_STATUS_DIR/unity-mcp-status-dummy.json" < /tmp/mcp-preflight.log 2>&1 || { cat /tmp/mcp-preflight.log; exit 1; } cat /tmp/mcp-preflight.log - - diff --git a/.github/workflows/claude-nl-suite.yml b/.github/workflows/claude-nl-suite.yml index 54faaa98c..3bffbae74 100644 --- a/.github/workflows/claude-nl-suite.yml +++ b/.github/workflows/claude-nl-suite.yml @@ -55,10 +55,10 @@ jobs: uv venv echo "VIRTUAL_ENV=$GITHUB_WORKSPACE/.venv" >> "$GITHUB_ENV" echo "$GITHUB_WORKSPACE/.venv/bin" >> "$GITHUB_PATH" - if [ -f Server/pyproject.toml ]; then - uv pip install -e Server - elif [ -f Server/requirements.txt ]; then - uv pip install -r Server/requirements.txt + if [ -f MCPForUnity/Server~/pyproject.toml ]; then + uv pip install -e "MCPForUnity/Server~" + elif [ -f MCPForUnity/Server~/requirements.txt ]; then + uv pip install -r MCPForUnity/Server~/requirements.txt else echo "No MCP Python deps found (skipping)" fi @@ -409,7 +409,7 @@ jobs: "run", "--active", "--directory", - "Server", + "MCPForUnity/Server~", "mcp-for-unity", "--transport", "stdio", @@ -512,7 +512,7 @@ jobs: attempt=0 while true; do attempt=$((attempt+1)) - if uv run --active --directory Server mcp-for-unity --transport stdio --help > /tmp/mcp-preflight.log 2>&1; then + if uv run --active --directory "MCPForUnity/Server~" mcp-for-unity --transport stdio --help > /tmp/mcp-preflight.log 2>&1; then cat /tmp/mcp-preflight.log break fi @@ -541,7 +541,7 @@ jobs: echo "--- PortDiscovery debug ---" python3 - <<'PY' import sys - sys.path.insert(0, "Server/src") + sys.path.insert(0, "MCPForUnity/Server~/src") from transport.legacy.port_discovery import PortDiscovery import json @@ -556,7 +556,7 @@ jobs: import json import subprocess cmd = [ - "uv", "run", "--active", "--directory", "Server", "python", "-c", + "uv", "run", "--active", "--directory", "MCPForUnity/Server~", "python", "-c", "from transport.legacy.stdio_port_registry import stdio_port_registry; " "inst = stdio_port_registry.get_instances(force_refresh=True); " "import json; print(json.dumps([{'id':i.id,'port':i.port} for i in inst]))" @@ -577,7 +577,7 @@ jobs: PY echo "=== Testing MCP server startup with --status-dir flag ===" - uv run --active --directory Server python <<'PYTEST' + uv run --active --directory "MCPForUnity/Server~" python <<'PYTEST' import os import sys import glob @@ -602,7 +602,7 @@ jobs: set -euxo pipefail echo "=== Unity container status ===" docker inspect -f '{{.State.Status}} {{.State.Running}}' unity-mcp || echo "Container not found!" - + echo "=== Raw socket probe to Unity ===" # Try raw TCP connect without Python overhead for host in 127.0.0.1 localhost; do @@ -613,10 +613,10 @@ jobs: echo "$host:6400 - FAILED" fi done - + echo "=== Netstat for port 6400 ===" docker exec unity-mcp netstat -tlnp 2>/dev/null | grep 6400 || ss -tlnp | grep 6400 || echo "No listener found on 6400" - + echo "=== Python probe with timing ===" python3 <<'PY' import socket, time diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 2269758cd..f94f22758 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -4,7 +4,7 @@ on: push: branches: ["**"] paths: - - Server/** + - MCPForUnity/Server~/** - .github/workflows/python-tests.yml workflow_dispatch: {} @@ -26,13 +26,13 @@ jobs: - name: Install dependencies run: | - cd Server + cd MCPForUnity/Server~ uv sync uv pip install -e ".[dev]" - name: Run tests run: | - cd Server + cd MCPForUnity/Server~ uv run pytest tests/ -v --tb=short - name: Upload test results @@ -41,5 +41,5 @@ jobs: with: name: pytest-results path: | - Server/.pytest_cache/ - Server/tests/ + MCPForUnity/Server~/.pytest_cache/ + MCPForUnity/Server~/tests/ diff --git a/Server/Dockerfile b/MCPForUnity/Server~/Dockerfile similarity index 100% rename from Server/Dockerfile rename to MCPForUnity/Server~/Dockerfile diff --git a/Server/README.md b/MCPForUnity/Server~/README.md similarity index 100% rename from Server/README.md rename to MCPForUnity/Server~/README.md diff --git a/Server/__init__.py b/MCPForUnity/Server~/__init__.py similarity index 100% rename from Server/__init__.py rename to MCPForUnity/Server~/__init__.py diff --git a/MCPForUnity/Server~/mcp_wrapper.py b/MCPForUnity/Server~/mcp_wrapper.py new file mode 100644 index 000000000..8f725c418 --- /dev/null +++ b/MCPForUnity/Server~/mcp_wrapper.py @@ -0,0 +1,121 @@ +""" +MCP Wrapper for Antigravity on Windows +Fixes the "invalid trailing data" error by removing \r characters from stdout. + +This wrapper is needed because Windows adds \r\n line endings which cause +JSON-RPC parsing errors in Antigravity's MCP client. + +Usage in mcp_config.json: +{ + "mcpServers": { + "unity-mcp": { + "disabled": false, + "command": "python", + "args": [ + "C:\\path\\to\\this\\file\\mcp_wrapper.py" + ] + } + } +} + +Credits: Solution by @gajzzs from https://github.com/CoplayDev/unity-mcp/issues/430 +""" + +import sys +import os +import subprocess +import threading + +# Set binary mode for stdin/stdout to handle raw bytes +import msvcrt +msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) +msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) + + +def forward_stdin(proc): + """Forward stdin from Antigravity to the MCP server process.""" + try: + while True: + line = sys.stdin.buffer.readline() + if not line: + break + proc.stdin.write(line) + proc.stdin.flush() + except: + pass + + +def convert_stdout(proc): + """ + Read stdout from MCP server and remove \r characters before forwarding to Antigravity. + This fixes the "invalid trailing data" error caused by Windows line endings. + """ + try: + while True: + line = proc.stdout.readline() + if not line: + break + # Convert \r\n to \n by removing all \r + cleaned = line.replace(b'\r', b'') + sys.stdout.buffer.write(cleaned) + sys.stdout.buffer.flush() + except: + pass + + +# IMPORTANT: Update this path to match your uvx installation +# You can find the correct path by running: where uvx +# Common locations: +# - WinGet: C:\Users\\AppData\Local\Microsoft\WinGet\Packages\astral-sh.uv_Microsoft.Winget.Source_8wekyb3d8bbwe\uvx.exe +# - Manual install: C:\Users\\.cargo\bin\uvx.exe +# - System-wide: C:\Program Files\uv\uvx.exe + +# Try to detect uvx path automatically +username = os.environ.get('USERNAME') +uvx_path = None + +if username: + # Try WinGet installation path (most common on Windows) + winget_path = rf"C:\Users\{username}\AppData\Local\Microsoft\WinGet\Packages\astral-sh.uv_Microsoft.Winget.Source_8wekyb3d8bbwe\uvx.exe" + if os.path.exists(winget_path): + uvx_path = winget_path + +# Fallback to 'uvx' command (assumes it's in PATH) +if not uvx_path: + uvx_path = "uvx" + +# Allow custom Git repository URL via environment variable +# This makes it easy to use your own fork when pushing to Git +# Set UNITY_MCP_GIT_URL environment variable to override +# Example: UNITY_MCP_GIT_URL=git+https://github.com/YourUsername/unity-mcp@main#subdirectory=Server +default_git_url = "git+https://github.com/choej2303/unity-mcp-gg@versionUp#subdirectory=Server" +git_url = os.environ.get('UNITY_MCP_GIT_URL', default_git_url) + +# Support command-line argument for git URL (overrides environment variable) +# Usage: python mcp_wrapper.py [git_url] +if len(sys.argv) > 1: + git_url = sys.argv[1] + +cmd = [ + uvx_path, + "--from", git_url, + "mcp-for-unity", "--transport", "stdio" +] + +proc = subprocess.Popen( + cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 +) + +t1 = threading.Thread(target=forward_stdin, args=(proc,), daemon=True) +t2 = threading.Thread(target=convert_stdout, args=(proc,), daemon=True) +t1.start() +t2.start() + +try: + sys.exit(proc.wait()) +except: + proc.terminate() diff --git a/MCPForUnity/Server~/pyproject.toml b/MCPForUnity/Server~/pyproject.toml new file mode 100644 index 000000000..abc70b5ad --- /dev/null +++ b/MCPForUnity/Server~/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "MCPForUnityServer" +version = "8.2.2" +description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)." +readme = "README.md" +requires-python = ">=3.11" +dependencies = [ + "httpx>=0.28.1", + "fastmcp>=2.13.3", + "mcp>=1.21.2", + "pydantic>=2.12.5", + "fastapi>=0.121.1", + "uvicorn>=0.38.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=9.0.2", + "pytest-asyncio>=1.3.0", +] + +[project.scripts] +mcp-for-unity = "main:main" + +[build-system] +requires = ["setuptools>=80.9.0", "wheel>=0.45.1"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/Server/pyrightconfig.json b/MCPForUnity/Server~/pyrightconfig.json similarity index 100% rename from Server/pyrightconfig.json rename to MCPForUnity/Server~/pyrightconfig.json diff --git a/Server/src/__init__.py b/MCPForUnity/Server~/src/__init__.py similarity index 100% rename from Server/src/__init__.py rename to MCPForUnity/Server~/src/__init__.py diff --git a/Server/src/core/__init__.py b/MCPForUnity/Server~/src/core/__init__.py similarity index 100% rename from Server/src/core/__init__.py rename to MCPForUnity/Server~/src/core/__init__.py diff --git a/Server/src/core/config.py b/MCPForUnity/Server~/src/core/config.py similarity index 100% rename from Server/src/core/config.py rename to MCPForUnity/Server~/src/core/config.py diff --git a/Server/src/core/logging_decorator.py b/MCPForUnity/Server~/src/core/logging_decorator.py similarity index 100% rename from Server/src/core/logging_decorator.py rename to MCPForUnity/Server~/src/core/logging_decorator.py diff --git a/Server/src/core/telemetry.py b/MCPForUnity/Server~/src/core/telemetry.py similarity index 99% rename from Server/src/core/telemetry.py rename to MCPForUnity/Server~/src/core/telemetry.py index 9579a5de2..f88ab29af 100644 --- a/Server/src/core/telemetry.py +++ b/MCPForUnity/Server~/src/core/telemetry.py @@ -24,7 +24,7 @@ from urllib.parse import urlparse import uuid -import tomli +import tomllib try: import httpx @@ -46,8 +46,8 @@ def _version_from_local_pyproject() -> str: continue try: with candidate.open("rb") as f: - data = tomli.load(f) - except (OSError, tomli.TOMLDecodeError): + data = tomllib.load(f) + except (OSError, tomllib.TOMLDecodeError): continue project_table = data.get("project") or {} diff --git a/Server/src/core/telemetry_decorator.py b/MCPForUnity/Server~/src/core/telemetry_decorator.py similarity index 100% rename from Server/src/core/telemetry_decorator.py rename to MCPForUnity/Server~/src/core/telemetry_decorator.py diff --git a/Server/src/main.py b/MCPForUnity/Server~/src/main.py similarity index 98% rename from Server/src/main.py rename to MCPForUnity/Server~/src/main.py index ee04d5351..eed455345 100644 --- a/Server/src/main.py +++ b/MCPForUnity/Server~/src/main.py @@ -61,7 +61,18 @@ # Never let logging setup break startup logger.debug("Failed to configure main logger file handler", exc_info=exc) # Quieten noisy third-party loggers to avoid clutter during stdio handshake -for noisy in ("httpx", "urllib3", "mcp.server.lowlevel.server"): +for noisy in ( + "httpx", + "urllib3", + "mcp.server.lowlevel.server", + "uvicorn", + "uvicorn.access", + "uvicorn.error", + "docket", + "docket.worker", + "fastmcp", # <--- 🚨 범인 검거 + "starlette" # <--- 혹시 모를 공범 +): try: logging.getLogger(noisy).setLevel( max(logging.WARNING, getattr(logging, config.log_level))) @@ -403,7 +414,7 @@ def main(): else: # Use stdio transport for traditional MCP logger.info("Starting FastMCP with stdio transport") - mcp.run(transport='stdio') + mcp.run(transport='stdio', show_banner=False) # Run the server diff --git a/Server/src/models/__init__.py b/MCPForUnity/Server~/src/models/__init__.py similarity index 100% rename from Server/src/models/__init__.py rename to MCPForUnity/Server~/src/models/__init__.py diff --git a/Server/src/models/models.py b/MCPForUnity/Server~/src/models/models.py similarity index 100% rename from Server/src/models/models.py rename to MCPForUnity/Server~/src/models/models.py diff --git a/Server/src/models/unity_response.py b/MCPForUnity/Server~/src/models/unity_response.py similarity index 100% rename from Server/src/models/unity_response.py rename to MCPForUnity/Server~/src/models/unity_response.py diff --git a/Server/src/routes/__init__.py b/MCPForUnity/Server~/src/routes/__init__.py similarity index 100% rename from Server/src/routes/__init__.py rename to MCPForUnity/Server~/src/routes/__init__.py diff --git a/Server/src/services/__init__.py b/MCPForUnity/Server~/src/services/__init__.py similarity index 100% rename from Server/src/services/__init__.py rename to MCPForUnity/Server~/src/services/__init__.py diff --git a/Server/src/services/custom_tool_service.py b/MCPForUnity/Server~/src/services/custom_tool_service.py similarity index 100% rename from Server/src/services/custom_tool_service.py rename to MCPForUnity/Server~/src/services/custom_tool_service.py diff --git a/Server/src/services/registry/__init__.py b/MCPForUnity/Server~/src/services/registry/__init__.py similarity index 100% rename from Server/src/services/registry/__init__.py rename to MCPForUnity/Server~/src/services/registry/__init__.py diff --git a/Server/src/services/registry/resource_registry.py b/MCPForUnity/Server~/src/services/registry/resource_registry.py similarity index 100% rename from Server/src/services/registry/resource_registry.py rename to MCPForUnity/Server~/src/services/registry/resource_registry.py diff --git a/Server/src/services/registry/tool_registry.py b/MCPForUnity/Server~/src/services/registry/tool_registry.py similarity index 100% rename from Server/src/services/registry/tool_registry.py rename to MCPForUnity/Server~/src/services/registry/tool_registry.py diff --git a/Server/src/services/resources/__init__.py b/MCPForUnity/Server~/src/services/resources/__init__.py similarity index 100% rename from Server/src/services/resources/__init__.py rename to MCPForUnity/Server~/src/services/resources/__init__.py diff --git a/Server/src/services/resources/active_tool.py b/MCPForUnity/Server~/src/services/resources/active_tool.py similarity index 100% rename from Server/src/services/resources/active_tool.py rename to MCPForUnity/Server~/src/services/resources/active_tool.py diff --git a/Server/src/services/resources/custom_tools.py b/MCPForUnity/Server~/src/services/resources/custom_tools.py similarity index 100% rename from Server/src/services/resources/custom_tools.py rename to MCPForUnity/Server~/src/services/resources/custom_tools.py diff --git a/Server/src/services/resources/editor_state.py b/MCPForUnity/Server~/src/services/resources/editor_state.py similarity index 100% rename from Server/src/services/resources/editor_state.py rename to MCPForUnity/Server~/src/services/resources/editor_state.py diff --git a/Server/src/services/resources/layers.py b/MCPForUnity/Server~/src/services/resources/layers.py similarity index 100% rename from Server/src/services/resources/layers.py rename to MCPForUnity/Server~/src/services/resources/layers.py diff --git a/Server/src/services/resources/menu_items.py b/MCPForUnity/Server~/src/services/resources/menu_items.py similarity index 100% rename from Server/src/services/resources/menu_items.py rename to MCPForUnity/Server~/src/services/resources/menu_items.py diff --git a/Server/src/services/resources/prefab_stage.py b/MCPForUnity/Server~/src/services/resources/prefab_stage.py similarity index 100% rename from Server/src/services/resources/prefab_stage.py rename to MCPForUnity/Server~/src/services/resources/prefab_stage.py diff --git a/Server/src/services/resources/project_info.py b/MCPForUnity/Server~/src/services/resources/project_info.py similarity index 100% rename from Server/src/services/resources/project_info.py rename to MCPForUnity/Server~/src/services/resources/project_info.py diff --git a/Server/src/services/resources/selection.py b/MCPForUnity/Server~/src/services/resources/selection.py similarity index 100% rename from Server/src/services/resources/selection.py rename to MCPForUnity/Server~/src/services/resources/selection.py diff --git a/Server/src/services/resources/tags.py b/MCPForUnity/Server~/src/services/resources/tags.py similarity index 100% rename from Server/src/services/resources/tags.py rename to MCPForUnity/Server~/src/services/resources/tags.py diff --git a/Server/src/services/resources/tests.py b/MCPForUnity/Server~/src/services/resources/tests.py similarity index 100% rename from Server/src/services/resources/tests.py rename to MCPForUnity/Server~/src/services/resources/tests.py diff --git a/Server/src/services/resources/unity_instances.py b/MCPForUnity/Server~/src/services/resources/unity_instances.py similarity index 100% rename from Server/src/services/resources/unity_instances.py rename to MCPForUnity/Server~/src/services/resources/unity_instances.py diff --git a/Server/src/services/resources/windows.py b/MCPForUnity/Server~/src/services/resources/windows.py similarity index 100% rename from Server/src/services/resources/windows.py rename to MCPForUnity/Server~/src/services/resources/windows.py diff --git a/Server/src/services/tools/__init__.py b/MCPForUnity/Server~/src/services/tools/__init__.py similarity index 90% rename from Server/src/services/tools/__init__.py rename to MCPForUnity/Server~/src/services/tools/__init__.py index 7fc4555f2..38198137d 100644 --- a/Server/src/services/tools/__init__.py +++ b/MCPForUnity/Server~/src/services/tools/__init__.py @@ -3,7 +3,7 @@ import logging import os from pathlib import Path -from typing import TypeVar +from typing import TypeVar, Any from fastmcp import Context, FastMCP from core.telemetry_decorator import telemetry_tool @@ -46,6 +46,12 @@ def register_all_tools(mcp: FastMCP): description = tool_info['description'] kwargs = tool_info['kwargs'] + # [FIX] Pydantic KeyError solution + # Force 'ctx' annotation to Any so Pydantic doesn't validate it + if 'ctx' in func.__annotations__: + func.__annotations__['ctx'] = Any + + # Apply the @mcp.tool decorator, telemetry, and logging wrapped = log_execution(tool_name, "Tool")(func) wrapped = telemetry_tool(tool_name)(wrapped) diff --git a/Server/src/services/tools/batch_execute.py b/MCPForUnity/Server~/src/services/tools/batch_execute.py similarity index 100% rename from Server/src/services/tools/batch_execute.py rename to MCPForUnity/Server~/src/services/tools/batch_execute.py diff --git a/Server/src/services/tools/debug_request_context.py b/MCPForUnity/Server~/src/services/tools/debug_request_context.py similarity index 100% rename from Server/src/services/tools/debug_request_context.py rename to MCPForUnity/Server~/src/services/tools/debug_request_context.py diff --git a/Server/src/services/tools/execute_custom_tool.py b/MCPForUnity/Server~/src/services/tools/execute_custom_tool.py similarity index 100% rename from Server/src/services/tools/execute_custom_tool.py rename to MCPForUnity/Server~/src/services/tools/execute_custom_tool.py diff --git a/Server/src/services/tools/execute_menu_item.py b/MCPForUnity/Server~/src/services/tools/execute_menu_item.py similarity index 100% rename from Server/src/services/tools/execute_menu_item.py rename to MCPForUnity/Server~/src/services/tools/execute_menu_item.py diff --git a/Server/src/services/tools/find_in_file.py b/MCPForUnity/Server~/src/services/tools/find_in_file.py similarity index 100% rename from Server/src/services/tools/find_in_file.py rename to MCPForUnity/Server~/src/services/tools/find_in_file.py diff --git a/Server/src/services/tools/manage_asset.py b/MCPForUnity/Server~/src/services/tools/manage_asset.py similarity index 100% rename from Server/src/services/tools/manage_asset.py rename to MCPForUnity/Server~/src/services/tools/manage_asset.py diff --git a/Server/src/services/tools/manage_editor.py b/MCPForUnity/Server~/src/services/tools/manage_editor.py similarity index 100% rename from Server/src/services/tools/manage_editor.py rename to MCPForUnity/Server~/src/services/tools/manage_editor.py diff --git a/Server/src/services/tools/manage_gameobject.py b/MCPForUnity/Server~/src/services/tools/manage_gameobject.py similarity index 100% rename from Server/src/services/tools/manage_gameobject.py rename to MCPForUnity/Server~/src/services/tools/manage_gameobject.py diff --git a/Server/src/services/tools/manage_material.py b/MCPForUnity/Server~/src/services/tools/manage_material.py similarity index 100% rename from Server/src/services/tools/manage_material.py rename to MCPForUnity/Server~/src/services/tools/manage_material.py diff --git a/Server/src/services/tools/manage_prefabs.py b/MCPForUnity/Server~/src/services/tools/manage_prefabs.py similarity index 100% rename from Server/src/services/tools/manage_prefabs.py rename to MCPForUnity/Server~/src/services/tools/manage_prefabs.py diff --git a/Server/src/services/tools/manage_scene.py b/MCPForUnity/Server~/src/services/tools/manage_scene.py similarity index 100% rename from Server/src/services/tools/manage_scene.py rename to MCPForUnity/Server~/src/services/tools/manage_scene.py diff --git a/Server/src/services/tools/manage_script.py b/MCPForUnity/Server~/src/services/tools/manage_script.py similarity index 100% rename from Server/src/services/tools/manage_script.py rename to MCPForUnity/Server~/src/services/tools/manage_script.py diff --git a/Server/src/services/tools/manage_shader.py b/MCPForUnity/Server~/src/services/tools/manage_shader.py similarity index 100% rename from Server/src/services/tools/manage_shader.py rename to MCPForUnity/Server~/src/services/tools/manage_shader.py diff --git a/Server/src/services/tools/read_console.py b/MCPForUnity/Server~/src/services/tools/read_console.py similarity index 100% rename from Server/src/services/tools/read_console.py rename to MCPForUnity/Server~/src/services/tools/read_console.py diff --git a/Server/src/services/tools/run_tests.py b/MCPForUnity/Server~/src/services/tools/run_tests.py similarity index 100% rename from Server/src/services/tools/run_tests.py rename to MCPForUnity/Server~/src/services/tools/run_tests.py diff --git a/Server/src/services/tools/script_apply_edits.py b/MCPForUnity/Server~/src/services/tools/script_apply_edits.py similarity index 100% rename from Server/src/services/tools/script_apply_edits.py rename to MCPForUnity/Server~/src/services/tools/script_apply_edits.py diff --git a/Server/src/services/tools/set_active_instance.py b/MCPForUnity/Server~/src/services/tools/set_active_instance.py similarity index 100% rename from Server/src/services/tools/set_active_instance.py rename to MCPForUnity/Server~/src/services/tools/set_active_instance.py diff --git a/Server/src/services/tools/utils.py b/MCPForUnity/Server~/src/services/tools/utils.py similarity index 100% rename from Server/src/services/tools/utils.py rename to MCPForUnity/Server~/src/services/tools/utils.py diff --git a/Server/src/transport/__init__.py b/MCPForUnity/Server~/src/transport/__init__.py similarity index 100% rename from Server/src/transport/__init__.py rename to MCPForUnity/Server~/src/transport/__init__.py diff --git a/Server/src/transport/legacy/port_discovery.py b/MCPForUnity/Server~/src/transport/legacy/port_discovery.py similarity index 100% rename from Server/src/transport/legacy/port_discovery.py rename to MCPForUnity/Server~/src/transport/legacy/port_discovery.py diff --git a/Server/src/transport/legacy/stdio_port_registry.py b/MCPForUnity/Server~/src/transport/legacy/stdio_port_registry.py similarity index 100% rename from Server/src/transport/legacy/stdio_port_registry.py rename to MCPForUnity/Server~/src/transport/legacy/stdio_port_registry.py diff --git a/Server/src/transport/legacy/unity_connection.py b/MCPForUnity/Server~/src/transport/legacy/unity_connection.py similarity index 100% rename from Server/src/transport/legacy/unity_connection.py rename to MCPForUnity/Server~/src/transport/legacy/unity_connection.py diff --git a/Server/src/transport/models.py b/MCPForUnity/Server~/src/transport/models.py similarity index 100% rename from Server/src/transport/models.py rename to MCPForUnity/Server~/src/transport/models.py diff --git a/Server/src/transport/plugin_hub.py b/MCPForUnity/Server~/src/transport/plugin_hub.py similarity index 100% rename from Server/src/transport/plugin_hub.py rename to MCPForUnity/Server~/src/transport/plugin_hub.py diff --git a/Server/src/transport/plugin_registry.py b/MCPForUnity/Server~/src/transport/plugin_registry.py similarity index 100% rename from Server/src/transport/plugin_registry.py rename to MCPForUnity/Server~/src/transport/plugin_registry.py diff --git a/Server/src/transport/unity_instance_middleware.py b/MCPForUnity/Server~/src/transport/unity_instance_middleware.py similarity index 100% rename from Server/src/transport/unity_instance_middleware.py rename to MCPForUnity/Server~/src/transport/unity_instance_middleware.py diff --git a/Server/src/transport/unity_transport.py b/MCPForUnity/Server~/src/transport/unity_transport.py similarity index 100% rename from Server/src/transport/unity_transport.py rename to MCPForUnity/Server~/src/transport/unity_transport.py diff --git a/Server/src/utils/module_discovery.py b/MCPForUnity/Server~/src/utils/module_discovery.py similarity index 100% rename from Server/src/utils/module_discovery.py rename to MCPForUnity/Server~/src/utils/module_discovery.py diff --git a/Server/src/utils/reload_sentinel.py b/MCPForUnity/Server~/src/utils/reload_sentinel.py similarity index 100% rename from Server/src/utils/reload_sentinel.py rename to MCPForUnity/Server~/src/utils/reload_sentinel.py diff --git a/Server/tests/__init__.py b/MCPForUnity/Server~/tests/__init__.py similarity index 100% rename from Server/tests/__init__.py rename to MCPForUnity/Server~/tests/__init__.py diff --git a/Server/tests/integration/__init__.py b/MCPForUnity/Server~/tests/integration/__init__.py similarity index 100% rename from Server/tests/integration/__init__.py rename to MCPForUnity/Server~/tests/integration/__init__.py diff --git a/Server/tests/integration/conftest.py b/MCPForUnity/Server~/tests/integration/conftest.py similarity index 100% rename from Server/tests/integration/conftest.py rename to MCPForUnity/Server~/tests/integration/conftest.py diff --git a/Server/tests/integration/test_domain_reload_resilience.py b/MCPForUnity/Server~/tests/integration/test_domain_reload_resilience.py similarity index 100% rename from Server/tests/integration/test_domain_reload_resilience.py rename to MCPForUnity/Server~/tests/integration/test_domain_reload_resilience.py diff --git a/Server/tests/integration/test_edit_normalization_and_noop.py b/MCPForUnity/Server~/tests/integration/test_edit_normalization_and_noop.py similarity index 100% rename from Server/tests/integration/test_edit_normalization_and_noop.py rename to MCPForUnity/Server~/tests/integration/test_edit_normalization_and_noop.py diff --git a/Server/tests/integration/test_edit_strict_and_warnings.py b/MCPForUnity/Server~/tests/integration/test_edit_strict_and_warnings.py similarity index 100% rename from Server/tests/integration/test_edit_strict_and_warnings.py rename to MCPForUnity/Server~/tests/integration/test_edit_strict_and_warnings.py diff --git a/Server/tests/integration/test_find_in_file_minimal.py b/MCPForUnity/Server~/tests/integration/test_find_in_file_minimal.py similarity index 100% rename from Server/tests/integration/test_find_in_file_minimal.py rename to MCPForUnity/Server~/tests/integration/test_find_in_file_minimal.py diff --git a/Server/tests/integration/test_get_sha.py b/MCPForUnity/Server~/tests/integration/test_get_sha.py similarity index 100% rename from Server/tests/integration/test_get_sha.py rename to MCPForUnity/Server~/tests/integration/test_get_sha.py diff --git a/Server/tests/integration/test_helpers.py b/MCPForUnity/Server~/tests/integration/test_helpers.py similarity index 100% rename from Server/tests/integration/test_helpers.py rename to MCPForUnity/Server~/tests/integration/test_helpers.py diff --git a/Server/tests/integration/test_improved_anchor_matching.py b/MCPForUnity/Server~/tests/integration/test_improved_anchor_matching.py similarity index 100% rename from Server/tests/integration/test_improved_anchor_matching.py rename to MCPForUnity/Server~/tests/integration/test_improved_anchor_matching.py diff --git a/Server/tests/integration/test_instance_routing_comprehensive.py b/MCPForUnity/Server~/tests/integration/test_instance_routing_comprehensive.py similarity index 100% rename from Server/tests/integration/test_instance_routing_comprehensive.py rename to MCPForUnity/Server~/tests/integration/test_instance_routing_comprehensive.py diff --git a/Server/tests/integration/test_instance_targeting_resolution.py b/MCPForUnity/Server~/tests/integration/test_instance_targeting_resolution.py similarity index 100% rename from Server/tests/integration/test_instance_targeting_resolution.py rename to MCPForUnity/Server~/tests/integration/test_instance_targeting_resolution.py diff --git a/Server/tests/integration/test_json_parsing_simple.py b/MCPForUnity/Server~/tests/integration/test_json_parsing_simple.py similarity index 100% rename from Server/tests/integration/test_json_parsing_simple.py rename to MCPForUnity/Server~/tests/integration/test_json_parsing_simple.py diff --git a/Server/tests/integration/test_logging_stdout.py b/MCPForUnity/Server~/tests/integration/test_logging_stdout.py similarity index 100% rename from Server/tests/integration/test_logging_stdout.py rename to MCPForUnity/Server~/tests/integration/test_logging_stdout.py diff --git a/Server/tests/integration/test_manage_asset_json_parsing.py b/MCPForUnity/Server~/tests/integration/test_manage_asset_json_parsing.py similarity index 100% rename from Server/tests/integration/test_manage_asset_json_parsing.py rename to MCPForUnity/Server~/tests/integration/test_manage_asset_json_parsing.py diff --git a/Server/tests/integration/test_manage_asset_param_coercion.py b/MCPForUnity/Server~/tests/integration/test_manage_asset_param_coercion.py similarity index 100% rename from Server/tests/integration/test_manage_asset_param_coercion.py rename to MCPForUnity/Server~/tests/integration/test_manage_asset_param_coercion.py diff --git a/Server/tests/integration/test_manage_gameobject_param_coercion.py b/MCPForUnity/Server~/tests/integration/test_manage_gameobject_param_coercion.py similarity index 100% rename from Server/tests/integration/test_manage_gameobject_param_coercion.py rename to MCPForUnity/Server~/tests/integration/test_manage_gameobject_param_coercion.py diff --git a/Server/tests/integration/test_manage_script_uri.py b/MCPForUnity/Server~/tests/integration/test_manage_script_uri.py similarity index 100% rename from Server/tests/integration/test_manage_script_uri.py rename to MCPForUnity/Server~/tests/integration/test_manage_script_uri.py diff --git a/Server/tests/integration/test_read_console_truncate.py b/MCPForUnity/Server~/tests/integration/test_read_console_truncate.py similarity index 100% rename from Server/tests/integration/test_read_console_truncate.py rename to MCPForUnity/Server~/tests/integration/test_read_console_truncate.py diff --git a/Server/tests/integration/test_read_resource_minimal.py b/MCPForUnity/Server~/tests/integration/test_read_resource_minimal.py similarity index 100% rename from Server/tests/integration/test_read_resource_minimal.py rename to MCPForUnity/Server~/tests/integration/test_read_resource_minimal.py diff --git a/Server/tests/integration/test_resources_api.py b/MCPForUnity/Server~/tests/integration/test_resources_api.py similarity index 100% rename from Server/tests/integration/test_resources_api.py rename to MCPForUnity/Server~/tests/integration/test_resources_api.py diff --git a/Server/tests/integration/test_script_editing.py b/MCPForUnity/Server~/tests/integration/test_script_editing.py similarity index 100% rename from Server/tests/integration/test_script_editing.py rename to MCPForUnity/Server~/tests/integration/test_script_editing.py diff --git a/Server/tests/integration/test_script_tools.py b/MCPForUnity/Server~/tests/integration/test_script_tools.py similarity index 100% rename from Server/tests/integration/test_script_tools.py rename to MCPForUnity/Server~/tests/integration/test_script_tools.py diff --git a/Server/tests/integration/test_telemetry_endpoint_validation.py b/MCPForUnity/Server~/tests/integration/test_telemetry_endpoint_validation.py similarity index 100% rename from Server/tests/integration/test_telemetry_endpoint_validation.py rename to MCPForUnity/Server~/tests/integration/test_telemetry_endpoint_validation.py diff --git a/Server/tests/integration/test_telemetry_queue_worker.py b/MCPForUnity/Server~/tests/integration/test_telemetry_queue_worker.py similarity index 100% rename from Server/tests/integration/test_telemetry_queue_worker.py rename to MCPForUnity/Server~/tests/integration/test_telemetry_queue_worker.py diff --git a/Server/tests/integration/test_telemetry_subaction.py b/MCPForUnity/Server~/tests/integration/test_telemetry_subaction.py similarity index 100% rename from Server/tests/integration/test_telemetry_subaction.py rename to MCPForUnity/Server~/tests/integration/test_telemetry_subaction.py diff --git a/Server/tests/integration/test_transport_framing.py b/MCPForUnity/Server~/tests/integration/test_transport_framing.py similarity index 100% rename from Server/tests/integration/test_transport_framing.py rename to MCPForUnity/Server~/tests/integration/test_transport_framing.py diff --git a/Server/tests/integration/test_validate_script_summary.py b/MCPForUnity/Server~/tests/integration/test_validate_script_summary.py similarity index 100% rename from Server/tests/integration/test_validate_script_summary.py rename to MCPForUnity/Server~/tests/integration/test_validate_script_summary.py diff --git a/Server/tests/pytest.ini b/MCPForUnity/Server~/tests/pytest.ini similarity index 100% rename from Server/tests/pytest.ini rename to MCPForUnity/Server~/tests/pytest.ini diff --git a/Server/uv.lock b/MCPForUnity/Server~/uv.lock similarity index 50% rename from Server/uv.lock rename to MCPForUnity/Server~/uv.lock index 832b733b1..23c84632a 100644 --- a/Server/uv.lock +++ b/MCPForUnity/Server~/uv.lock @@ -1,6 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.10" +requires-python = ">=3.11" [[package]] name = "annotated-doc" @@ -22,17 +22,24 @@ wheels = [ [[package]] name = "anyio" -version = "4.9.0" +version = "4.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "idna" }, - { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/ce/8a777047513153587e5434fd752e89334ac33e379aa3497db860eeb60377/anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0", size = 228266, upload-time = "2025-11-28T23:37:38.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/7f/9c/36c5c37947ebfb8c7f22e0eb6e4d188ee2d53aa3880f3f2744fb894f0cb1/anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb", size = 113362, upload-time = "2025-11-28T23:36:57.897Z" }, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" }, ] [[package]] @@ -46,23 +53,14 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.5" +version = "1.6.6" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" }, -] - -[[package]] -name = "backports-asyncio-runner" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/ff/70dca7d7cb1cbc0edb2c6cc0c38b65cba36cccc491eca64cabd5fe7f8670/backports_asyncio_runner-1.2.0.tar.gz", hash = "sha256:a5aa7b2b7d8f8bfcaa2b57313f70792df84e32a2a746f585213373f900b42162", size = 69893, upload-time = "2025-07-02T02:27:15.685Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/9b/b1661026ff24bc641b76b78c5222d614776b0c085bcfdac9bd15a1cb4b35/authlib-1.6.6.tar.gz", hash = "sha256:45770e8e056d0f283451d9996fbb59b70d45722b45d854d58f32878d0a40c38e", size = 164894, upload-time = "2025-12-12T08:01:41.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/59/76ab57e3fe74484f48a53f8e337171b4a2349e506eabe136d7e01d059086/backports_asyncio_runner-1.2.0-py3-none-any.whl", hash = "sha256:0da0a936a8aeb554eccb426dc55af3ba63bcdc69fa1a600b5bb305413a4477b5", size = 12313, upload-time = "2025-07-02T02:27:14.263Z" }, + { url = "https://files.pythonhosted.org/packages/54/51/321e821856452f7386c4e9df866f196720b1ad0c5ea1623ea7399969ae3b/authlib-1.6.6-py2.py3-none-any.whl", hash = "sha256:7d9e9bc535c13974313a87f53e8430eb6ea3d1cf6ae4f6efcd793f2e949143fd", size = 244005, upload-time = "2025-12-12T08:01:40.209Z" }, ] [[package]] @@ -76,29 +74,29 @@ wheels = [ [[package]] name = "beartype" -version = "0.22.5" +version = "0.22.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/09/9003e5662691056e0e8b2e6f57c799e71875fac0be0e785d8cb11557cd2a/beartype-0.22.5.tar.gz", hash = "sha256:516a9096cc77103c96153474fa35c3ebcd9d36bd2ec8d0e3a43307ced0fa6341", size = 1586256, upload-time = "2025-11-01T05:49:20.771Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/1d/794ae2acaa67c8b216d91d5919da2606c2bb14086849ffde7f5555f3a3a5/beartype-0.22.8.tar.gz", hash = "sha256:b19b21c9359722ee3f7cc433f063b3e13997b27ae8226551ea5062e621f61165", size = 1602262, upload-time = "2025-12-03T05:11:10.766Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/f6/073d19f7b571c08327fbba3f8e011578da67ab62a11f98911274ff80653f/beartype-0.22.5-py3-none-any.whl", hash = "sha256:d9743dd7cd6d193696eaa1e025f8a70fb09761c154675679ff236e61952dfba0", size = 1321700, upload-time = "2025-11-01T05:49:18.436Z" }, + { url = "https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl", hash = "sha256:b832882d04e41a4097bab9f63e6992bc6de58c414ee84cba9b45b67314f5ab2e", size = 1331895, upload-time = "2025-12-03T05:11:08.373Z" }, ] [[package]] name = "cachetools" -version = "6.2.1" +version = "6.2.3" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/44/5dc354b9f2df614673c2a542a630ef95d578b4a8673a1046d1137a7e2453/cachetools-6.2.3.tar.gz", hash = "sha256:64e0a4ddf275041dd01f5b873efa87c91ea49022b844b8c5d1ad3407c0f42f1f", size = 31641, upload-time = "2025-12-12T21:18:06.011Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/ab/de/aa4cfc69feb5b3d604310214369979bb222ed0df0e2575a1b6e7af1a5579/cachetools-6.2.3-py3-none-any.whl", hash = "sha256:3fde34f7033979efb1e79b07ae529c2c40808bdd23b0b731405a48439254fba5", size = 11554, upload-time = "2025-12-12T21:18:04.556Z" }, ] [[package]] name = "certifi" -version = "2025.1.31" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577, upload-time = "2025-01-31T02:16:47.166Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393, upload-time = "2025-01-31T02:16:45.015Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] @@ -110,18 +108,6 @@ dependencies = [ ] 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/93/d7/516d984057745a6cd96575eea814fe1edd6646ee6efd552fb7b0921dec83/cffi-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:0cf2d91ecc3fcc0625c2c530fe004f82c110405f101548512cce44322fa8ac44", size = 184283, upload-time = "2025-09-08T23:22:08.01Z" }, - { url = "https://files.pythonhosted.org/packages/9e/84/ad6a0b408daa859246f57c03efd28e5dd1b33c21737c2db84cae8c237aa5/cffi-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f73b96c41e3b2adedc34a7356e64c8eb96e03a3782b535e043a986276ce12a49", size = 180504, upload-time = "2025-09-08T23:22:10.637Z" }, - { url = "https://files.pythonhosted.org/packages/50/bd/b1a6362b80628111e6653c961f987faa55262b4002fcec42308cad1db680/cffi-2.0.0-cp310-cp310-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:53f77cbe57044e88bbd5ed26ac1d0514d2acf0591dd6bb02a3ae37f76811b80c", size = 208811, upload-time = "2025-09-08T23:22:12.267Z" }, - { url = "https://files.pythonhosted.org/packages/4f/27/6933a8b2562d7bd1fb595074cf99cc81fc3789f6a6c05cdabb46284a3188/cffi-2.0.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3e837e369566884707ddaf85fc1744b47575005c0a229de3327f8f9a20f4efeb", size = 216402, upload-time = "2025-09-08T23:22:13.455Z" }, - { url = "https://files.pythonhosted.org/packages/05/eb/b86f2a2645b62adcfff53b0dd97e8dfafb5c8aa864bd0d9a2c2049a0d551/cffi-2.0.0-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5eda85d6d1879e692d546a078b44251cdd08dd1cfb98dfb77b670c97cee49ea0", size = 203217, upload-time = "2025-09-08T23:22:14.596Z" }, - { url = "https://files.pythonhosted.org/packages/9f/e0/6cbe77a53acf5acc7c08cc186c9928864bd7c005f9efd0d126884858a5fe/cffi-2.0.0-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9332088d75dc3241c702d852d4671613136d90fa6881da7d770a483fd05248b4", size = 203079, upload-time = "2025-09-08T23:22:15.769Z" }, - { url = "https://files.pythonhosted.org/packages/98/29/9b366e70e243eb3d14a5cb488dfd3a0b6b2f1fb001a203f653b93ccfac88/cffi-2.0.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc7de24befaeae77ba923797c7c87834c73648a05a4bde34b3b7e5588973a453", size = 216475, upload-time = "2025-09-08T23:22:17.427Z" }, - { url = "https://files.pythonhosted.org/packages/21/7a/13b24e70d2f90a322f2900c5d8e1f14fa7e2a6b3332b7309ba7b2ba51a5a/cffi-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf364028c016c03078a23b503f02058f1814320a56ad535686f90565636a9495", size = 218829, upload-time = "2025-09-08T23:22:19.069Z" }, - { url = "https://files.pythonhosted.org/packages/60/99/c9dc110974c59cc981b1f5b66e1d8af8af764e00f0293266824d9c4254bc/cffi-2.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e11e82b744887154b182fd3e7e8512418446501191994dbf9c9fc1f32cc8efd5", size = 211211, upload-time = "2025-09-08T23:22:20.588Z" }, - { url = "https://files.pythonhosted.org/packages/49/72/ff2d12dbf21aca1b32a40ed792ee6b40f6dc3a9cf1644bd7ef6e95e0ac5e/cffi-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8ea985900c5c95ce9db1745f7933eeef5d314f0565b27625d9a10ec9881e1bfb", size = 218036, upload-time = "2025-09-08T23:22:22.143Z" }, - { url = "https://files.pythonhosted.org/packages/e2/cc/027d7fb82e58c48ea717149b03bcadcbdc293553edb283af792bd4bcbb3f/cffi-2.0.0-cp310-cp310-win32.whl", hash = "sha256:1f72fb8906754ac8a2cc3f9f5aaa298070652a0ffae577e0ea9bd480dc3c931a", size = 172184, upload-time = "2025-09-08T23:22:23.328Z" }, - { url = "https://files.pythonhosted.org/packages/33/fa/072dd15ae27fbb4e06b437eb6e944e75b068deb09e2a2826039e49ee2045/cffi-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b18a3ed7d5b3bd8d9ef7a8cb226502c6bf8308df1525e1cc676c3680e7176739", size = 182790, upload-time = "2025-09-08T23:22:24.752Z" }, { 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" }, @@ -189,22 +175,6 @@ 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/1f/b8/6d51fc1d52cbd52cd4ccedd5b5b2f0f6a11bbf6765c782298b0f3e808541/charset_normalizer-3.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", size = 209709, upload-time = "2025-10-14T04:40:11.385Z" }, - { url = "https://files.pythonhosted.org/packages/5c/af/1f9d7f7faafe2ddfb6f72a2e07a548a629c61ad510fe60f9630309908fef/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", size = 148814, upload-time = "2025-10-14T04:40:13.135Z" }, - { url = "https://files.pythonhosted.org/packages/79/3d/f2e3ac2bbc056ca0c204298ea4e3d9db9b4afe437812638759db2c976b5f/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", size = 144467, upload-time = "2025-10-14T04:40:14.728Z" }, - { url = "https://files.pythonhosted.org/packages/ec/85/1bf997003815e60d57de7bd972c57dc6950446a3e4ccac43bc3070721856/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", size = 162280, upload-time = "2025-10-14T04:40:16.14Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8e/6aa1952f56b192f54921c436b87f2aaf7c7a7c3d0d1a765547d64fd83c13/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", size = 159454, upload-time = "2025-10-14T04:40:17.567Z" }, - { url = "https://files.pythonhosted.org/packages/36/3b/60cbd1f8e93aa25d1c669c649b7a655b0b5fb4c571858910ea9332678558/charset_normalizer-3.4.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", size = 153609, upload-time = "2025-10-14T04:40:19.08Z" }, - { url = "https://files.pythonhosted.org/packages/64/91/6a13396948b8fd3c4b4fd5bc74d045f5637d78c9675585e8e9fbe5636554/charset_normalizer-3.4.4-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", size = 151849, upload-time = "2025-10-14T04:40:20.607Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7a/59482e28b9981d105691e968c544cc0df3b7d6133152fb3dcdc8f135da7a/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", size = 151586, upload-time = "2025-10-14T04:40:21.719Z" }, - { url = "https://files.pythonhosted.org/packages/92/59/f64ef6a1c4bdd2baf892b04cd78792ed8684fbc48d4c2afe467d96b4df57/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", size = 145290, upload-time = "2025-10-14T04:40:23.069Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/3bf9f279ddfa641ffa1962b0db6a57a9c294361cc2f5fcac997049a00e9c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", size = 163663, upload-time = "2025-10-14T04:40:24.17Z" }, - { url = "https://files.pythonhosted.org/packages/ed/09/c9e38fc8fa9e0849b172b581fd9803bdf6e694041127933934184e19f8c3/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", size = 151964, upload-time = "2025-10-14T04:40:25.368Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d1/d28b747e512d0da79d8b6a1ac18b7ab2ecfd81b2944c4c710e166d8dd09c/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", size = 161064, upload-time = "2025-10-14T04:40:26.806Z" }, - { url = "https://files.pythonhosted.org/packages/bb/9a/31d62b611d901c3b9e5500c36aab0ff5eb442043fb3a1c254200d3d397d9/charset_normalizer-3.4.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", size = 155015, upload-time = "2025-10-14T04:40:28.284Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f3/107e008fa2bff0c8b9319584174418e5e5285fef32f79d8ee6a430d0039c/charset_normalizer-3.4.4-cp310-cp310-win32.whl", hash = "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", size = 99792, upload-time = "2025-10-14T04:40:29.613Z" }, - { url = "https://files.pythonhosted.org/packages/eb/66/e396e8a408843337d7315bab30dbf106c38966f1819f123257f5520f8a96/charset_normalizer-3.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", size = 107198, upload-time = "2025-10-14T04:40:30.644Z" }, - { url = "https://files.pythonhosted.org/packages/b5/58/01b4f815bf0312704c267f2ccb6e5d42bcc7752340cd487bc9f8c3710597/charset_normalizer-3.4.4-cp310-cp310-win_arm64.whl", hash = "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", size = 100262, upload-time = "2025-10-14T04:40:32.108Z" }, { 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" }, @@ -274,14 +244,23 @@ wheels = [ [[package]] name = "click" -version = "8.1.8" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593, upload-time = "2024-12-21T18:38:44.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "cloudpickle" +version = "3.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188, upload-time = "2024-12-21T18:38:41.666Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] @@ -299,7 +278,6 @@ version = "46.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/33/c00162f49c0e2fe8064a62cb92b93e50c74a72bc370ab92f86112b33ff62/cryptography-46.0.3.tar.gz", hash = "sha256:a8b17438104fed022ce745b362294d9ce35b4c2e45c1d958ad4a4b019285f4a1", size = 749258, upload-time = "2025-10-15T23:18:31.74Z" } wheels = [ @@ -348,8 +326,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/6e/1c8331ddf91ca4730ab3086a0f1be19c65510a33b5a441cb334e7a2d2560/cryptography-46.0.3-cp38-abi3-win32.whl", hash = "sha256:6276eb85ef938dc035d59b87c8a7dc559a232f954962520137529d77b18ff1df", size = 3036695, upload-time = "2025-10-15T23:18:08.672Z" }, { url = "https://files.pythonhosted.org/packages/90/45/b0d691df20633eff80955a0fc7695ff9051ffce8b69741444bd9ed7bd0db/cryptography-46.0.3-cp38-abi3-win_amd64.whl", hash = "sha256:416260257577718c05135c55958b674000baef9a1c7d9e8f306ec60d71db850f", size = 3501720, upload-time = "2025-10-15T23:18:10.632Z" }, { url = "https://files.pythonhosted.org/packages/e8/cb/2da4cc83f5edb9c3257d09e1e7ab7b23f049c7962cae8d842bbef0a9cec9/cryptography-46.0.3-cp38-abi3-win_arm64.whl", hash = "sha256:d89c3468de4cdc4f08a57e214384d0471911a3830fcdaf7a8cc587e42a866372", size = 2918740, upload-time = "2025-10-15T23:18:12.277Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cd/1a8633802d766a0fa46f382a77e096d7e209e0817892929655fe0586ae32/cryptography-46.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a23582810fedb8c0bc47524558fb6c56aac3fc252cb306072fd2815da2a47c32", size = 3689163, upload-time = "2025-10-15T23:18:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/4c/59/6b26512964ace6480c3e54681a9859c974172fb141c38df11eadd8416947/cryptography-46.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e7aec276d68421f9574040c26e2a7c3771060bc0cff408bae1dcb19d3ab1e63c", size = 3429474, upload-time = "2025-10-15T23:18:15.477Z" }, { url = "https://files.pythonhosted.org/packages/06/8a/e60e46adab4362a682cf142c7dcb5bf79b782ab2199b0dcb81f55970807f/cryptography-46.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7ce938a99998ed3c8aa7e7272dca1a610401ede816d36d0693907d863b10d9ea", size = 3698132, upload-time = "2025-10-15T23:18:17.056Z" }, { url = "https://files.pythonhosted.org/packages/da/38/f59940ec4ee91e93d3311f7532671a5cef5570eb04a144bf203b58552d11/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:191bb60a7be5e6f54e30ba16fdfae78ad3a342a0599eb4193ba88e3f3d6e185b", size = 4243992, upload-time = "2025-10-15T23:18:18.695Z" }, { url = "https://files.pythonhosted.org/packages/b0/0c/35b3d92ddebfdfda76bb485738306545817253d0a3ded0bfe80ef8e67aa5/cryptography-46.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c70cc23f12726be8f8bc72e41d5065d77e4515efae3690326764ea1b07845cfb", size = 4409944, upload-time = "2025-10-15T23:18:20.597Z" }, @@ -360,19 +336,17 @@ wheels = [ [[package]] name = "cyclopts" -version = "4.0.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "docstring-parser" }, { name = "rich" }, { name = "rich-rst" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/d1/2f2b99ec5ea54ac18baadfc4a011e2a1743c1eaae1e39838ca520dcf4811/cyclopts-4.0.0.tar.gz", hash = "sha256:0dae712085e91d32cc099ea3d78f305b0100a3998b1dec693be9feb0b1be101f", size = 143546, upload-time = "2025-10-20T18:33:01.456Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/0f/fe026df2ab8301e30a2b0bd425ff1462ad858fd4f991c1ac0389c2059c24/cyclopts-4.3.0.tar.gz", hash = "sha256:e95179cd0a959ce250ecfb2f0262a5996a92c1f9467bccad2f3d829e6833cef5", size = 151411, upload-time = "2025-11-25T02:59:33.572Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/44/0e/0a22e076944600aeb06f40b7e03bbd762a42d56d43a2f5f4ab954aed9005/cyclopts-4.0.0-py3-none-any.whl", hash = "sha256:e64801a2c86b681f08323fd50110444ee961236a0bae402a66d2cc3feda33da7", size = 178837, upload-time = "2025-10-20T18:33:00.191Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e8/77a231ae531cf38765b75ddf27dae28bb5f70b41d8bb4f15ce1650e93f57/cyclopts-4.3.0-py3-none-any.whl", hash = "sha256:91a30b69faf128ada7cfeaefd7d9649dc222e8b2a8697f1fc99e4ee7b7ca44f3", size = 187184, upload-time = "2025-11-25T02:59:32.21Z" }, ] [[package]] @@ -404,11 +378,11 @@ wheels = [ [[package]] name = "docutils" -version = "0.22.2" +version = "0.22.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/c0/89fe6215b443b919cb98a5002e107cb5026854ed1ccb6b5833e0768419d1/docutils-0.22.2.tar.gz", hash = "sha256:9fdb771707c8784c8f2728b67cb2c691305933d68137ef95a75db5f4dfbc213d", size = 2289092, upload-time = "2025-09-20T17:55:47.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/02/111134bfeb6e6c7ac4c74594e39a59f6c0195dc4846afbeac3cba60f1927/docutils-0.22.3.tar.gz", hash = "sha256:21486ae730e4ca9f622677b1412b879af1791efcfba517e4c6f60be543fc8cdd", size = 2290153, upload-time = "2025-11-06T02:35:55.655Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/dd/f95350e853a4468ec37478414fc04ae2d61dad7a947b3015c3dcc51a09b9/docutils-0.22.2-py3-none-any.whl", hash = "sha256:b0e98d679283fc3bb0ead8a5da7f501baa632654e7056e9c5846842213d674d8", size = 632667, upload-time = "2025-09-20T17:55:43.052Z" }, + { url = "https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl", hash = "sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb", size = 633032, upload-time = "2025-11-06T02:35:52.391Z" }, ] [[package]] @@ -426,19 +400,37 @@ wheels = [ [[package]] name = "exceptiongroup" -version = "1.3.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + +[[package]] +name = "fakeredis" +version = "2.32.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "redis" }, + { name = "sortedcontainers" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/56/14/b47b8471303af7deed7080290c14cff27a831fa47b38f45643e6bf889cee/fakeredis-2.32.1.tar.gz", hash = "sha256:dd8246db159f0b66a1ced7800c9d5ef07769e3d2fde44b389a57f2ce2834e444", size = 171582, upload-time = "2025-11-06T01:40:57.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/c2/d2/c28f6909864bfdb7411bb8f39fabedb5a50da1cbd7da5a1a3a46dfea2eab/fakeredis-2.32.1-py3-none-any.whl", hash = "sha256:e80c8886db2e47ba784f7dfe66aad6cd2eab76093c6bfda50041e5bc890d46cf", size = 118964, upload-time = "2025-11-06T01:40:55.885Z" }, +] + +[package.optional-dependencies] +lua = [ + { name = "lupa" }, ] [[package]] name = "fastapi" -version = "0.121.2" +version = "0.124.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, @@ -446,14 +438,14 @@ dependencies = [ { name = "starlette" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/48/f08f264da34cf160db82c62ffb335e838b1fc16cbcc905f474c7d4c815db/fastapi-0.121.2.tar.gz", hash = "sha256:ca8e932b2b823ec1721c641e3669472c855ad9564a2854c9899d904c2848b8b9", size = 342944, upload-time = "2025-11-13T17:05:54.692Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/21/ade3ff6745a82ea8ad88552b4139d27941549e4f19125879f848ac8f3c3d/fastapi-0.124.4.tar.gz", hash = "sha256:0e9422e8d6b797515f33f500309f6e1c98ee4e85563ba0f2debb282df6343763", size = 378460, upload-time = "2025-12-12T15:00:43.891Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/23/dfb161e91db7c92727db505dc72a384ee79681fe0603f706f9f9f52c2901/fastapi-0.121.2-py3-none-any.whl", hash = "sha256:f2d80b49a86a846b70cc3a03eb5ea6ad2939298bf6a7fe377aa9cd3dd079d358", size = 109201, upload-time = "2025-11-13T17:05:52.718Z" }, + { url = "https://files.pythonhosted.org/packages/3e/57/aa70121b5008f44031be645a61a7c4abc24e0e888ad3fc8fda916f4d188e/fastapi-0.124.4-py3-none-any.whl", hash = "sha256:6d1e703698443ccb89e50abe4893f3c84d9d6689c0cf1ca4fad6d3c15cf69f15", size = 113281, upload-time = "2025-12-12T15:00:42.44Z" }, ] [[package]] name = "fastmcp" -version = "2.13.0.2" +version = "2.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "authlib" }, @@ -466,36 +458,38 @@ dependencies = [ { name = "platformdirs" }, { name = "py-key-value-aio", extra = ["disk", "keyring", "memory"] }, { name = "pydantic", extra = ["email"] }, + { name = "pydocket" }, { name = "pyperclip" }, { name = "python-dotenv" }, { name = "rich" }, + { name = "uvicorn" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/74/584a152bcd174c99ddf3cfdd7e86ec4a6c696fb190a907c2a2ec9056bda2/fastmcp-2.13.0.2.tar.gz", hash = "sha256:d35386561b6f3cde195ba2b5892dc89b8919a721e6b39b98e7a16f9a7c0b8e8b", size = 7762083, upload-time = "2025-10-28T13:56:21.702Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/50/9bb042a2d290ccadb35db3580ac507f192e1a39c489eb8faa167cd5e3b57/fastmcp-2.14.0.tar.gz", hash = "sha256:c1f487b36a3e4b043dbf3330e588830047df2e06f8ef0920d62dfb34d0905727", size = 8232562, upload-time = "2025-12-11T23:04:27.134Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/c6/95eacd687cfab64fec13bfb64e6c6e7da13d01ecd4cb7d7e991858a08119/fastmcp-2.13.0.2-py3-none-any.whl", hash = "sha256:eb381eb073a101aabbc0ac44b05e23fef0cd1619344b7703115c825c8755fa1c", size = 367511, upload-time = "2025-10-28T13:56:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/54/73/b5656172a6beb2eacec95f04403ddea1928e4b22066700fd14780f8f45d1/fastmcp-2.14.0-py3-none-any.whl", hash = "sha256:7b374c0bcaf1ef1ef46b9255ea84c607f354291eaf647ff56a47c69f5ec0c204", size = 398965, upload-time = "2025-12-11T23:04:25.587Z" }, ] [[package]] name = "h11" -version = "0.14.0" +version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418, upload-time = "2022-09-25T15:40:01.519Z" } +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/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259, upload-time = "2022-09-25T15:39:59.68Z" }, + { 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 = "httpcore" -version = "1.0.7" +version = "1.0.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196, upload-time = "2024-11-15T12:30:47.531Z" } +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/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551, upload-time = "2024-11-15T12:30:45.782Z" }, + { 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]] @@ -515,20 +509,20 @@ wheels = [ [[package]] name = "httpx-sse" -version = "0.4.0" +version = "0.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624, upload-time = "2023-12-22T08:01:21.083Z" } +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/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819, upload-time = "2023-12-22T08:01:19.89Z" }, + { 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 = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +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/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { 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]] @@ -657,21 +651,84 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl", hash = "sha256:be4a0b195f149690c166e850609a477c532ddbfbaed96a404d4e43f8d5e2689f", size = 39160, upload-time = "2025-11-16T16:26:08.402Z" }, ] +[[package]] +name = "lupa" +version = "2.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/1c/191c3e6ec6502e3dbe25a53e27f69a5daeac3e56de1f73c0138224171ead/lupa-2.6.tar.gz", hash = "sha256:9a770a6e89576be3447668d7ced312cd6fd41d3c13c2462c9dc2c2ab570e45d9", size = 7240282, upload-time = "2025-10-24T07:20:29.738Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/29/1f66907c1ebf1881735afa695e646762c674f00738ebf66d795d59fc0665/lupa-2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6d988c0f9331b9f2a5a55186701a25444ab10a1432a1021ee58011499ecbbdd5", size = 962875, upload-time = "2025-10-24T07:17:39.107Z" }, + { url = "https://files.pythonhosted.org/packages/e6/67/4a748604be360eb9c1c215f6a0da921cd1a2b44b2c5951aae6fb83019d3a/lupa-2.6-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:ebe1bbf48259382c72a6fe363dea61a0fd6fe19eab95e2ae881e20f3654587bf", size = 1935390, upload-time = "2025-10-24T07:17:41.427Z" }, + { url = "https://files.pythonhosted.org/packages/ac/0c/8ef9ee933a350428b7bdb8335a37ef170ab0bb008bbf9ca8f4f4310116b6/lupa-2.6-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:a8fcee258487cf77cdd41560046843bb38c2e18989cd19671dd1e2596f798306", size = 992193, upload-time = "2025-10-24T07:17:43.231Z" }, + { url = "https://files.pythonhosted.org/packages/65/46/e6c7facebdb438db8a65ed247e56908818389c1a5abbf6a36aab14f1057d/lupa-2.6-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:561a8e3be800827884e767a694727ed8482d066e0d6edfcbf423b05e63b05535", size = 1165844, upload-time = "2025-10-24T07:17:45.437Z" }, + { url = "https://files.pythonhosted.org/packages/1c/26/9f1154c6c95f175ccbf96aa96c8f569c87f64f463b32473e839137601a8b/lupa-2.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af880a62d47991cae78b8e9905c008cbfdc4a3a9723a66310c2634fc7644578c", size = 1048069, upload-time = "2025-10-24T07:17:47.181Z" }, + { url = "https://files.pythonhosted.org/packages/68/67/2cc52ab73d6af81612b2ea24c870d3fa398443af8e2875e5befe142398b1/lupa-2.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:80b22923aa4023c86c0097b235615f89d469a0c4eee0489699c494d3367c4c85", size = 2079079, upload-time = "2025-10-24T07:17:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/2e/dc/f843f09bbf325f6e5ee61730cf6c3409fc78c010d968c7c78acba3019ca7/lupa-2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:153d2cc6b643f7efb9cfc0c6bb55ec784d5bac1a3660cfc5b958a7b8f38f4a75", size = 1071428, upload-time = "2025-10-24T07:17:51.991Z" }, + { url = "https://files.pythonhosted.org/packages/2e/60/37533a8d85bf004697449acb97ecdacea851acad28f2ad3803662487dd2a/lupa-2.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3fa8777e16f3ded50b72967dc17e23f5a08e4f1e2c9456aff2ebdb57f5b2869f", size = 1181756, upload-time = "2025-10-24T07:17:53.752Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/cf29b20dbb4927b6a3d27c339ac5d73e74306ecc28c8e2c900b2794142ba/lupa-2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8dbdcbe818c02a2f56f5ab5ce2de374dab03e84b25266cfbaef237829bc09b3f", size = 2175687, upload-time = "2025-10-24T07:17:56.228Z" }, + { url = "https://files.pythonhosted.org/packages/94/7c/050e02f80c7131b63db1474bff511e63c545b5a8636a24cbef3fc4da20b6/lupa-2.6-cp311-cp311-win32.whl", hash = "sha256:defaf188fde8f7a1e5ce3a5e6d945e533b8b8d547c11e43b96c9b7fe527f56dc", size = 1412592, upload-time = "2025-10-24T07:17:59.062Z" }, + { url = "https://files.pythonhosted.org/packages/6f/9a/6f2af98aa5d771cea661f66c8eb8f53772ec1ab1dfbce24126cfcd189436/lupa-2.6-cp311-cp311-win_amd64.whl", hash = "sha256:9505ae600b5c14f3e17e70f87f88d333717f60411faca1ddc6f3e61dce85fa9e", size = 1669194, upload-time = "2025-10-24T07:18:01.647Z" }, + { url = "https://files.pythonhosted.org/packages/94/86/ce243390535c39d53ea17ccf0240815e6e457e413e40428a658ea4ee4b8d/lupa-2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47ce718817ef1cc0c40d87c3d5ae56a800d61af00fbc0fad1ca9be12df2f3b56", size = 951707, upload-time = "2025-10-24T07:18:03.884Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/cedea5e6cbeb54396fdcc55f6b741696f3f036d23cfaf986d50d680446da/lupa-2.6-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7aba985b15b101495aa4b07112cdc08baa0c545390d560ad5cfde2e9e34f4d58", size = 1916703, upload-time = "2025-10-24T07:18:05.6Z" }, + { url = "https://files.pythonhosted.org/packages/24/be/3d6b5f9a8588c01a4d88129284c726017b2089f3a3fd3ba8bd977292fea0/lupa-2.6-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:b766f62f95b2739f2248977d29b0722e589dcf4f0ccfa827ccbd29f0148bd2e5", size = 985152, upload-time = "2025-10-24T07:18:08.561Z" }, + { url = "https://files.pythonhosted.org/packages/eb/23/9f9a05beee5d5dce9deca4cb07c91c40a90541fc0a8e09db4ee670da550f/lupa-2.6-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:00a934c23331f94cb51760097ebfab14b005d55a6b30a2b480e3c53dd2fa290d", size = 1159599, upload-time = "2025-10-24T07:18:10.346Z" }, + { url = "https://files.pythonhosted.org/packages/40/4e/e7c0583083db9d7f1fd023800a9767d8e4391e8330d56c2373d890ac971b/lupa-2.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21de9f38bd475303e34a042b7081aabdf50bd9bafd36ce4faea2f90fd9f15c31", size = 1038686, upload-time = "2025-10-24T07:18:12.112Z" }, + { url = "https://files.pythonhosted.org/packages/1c/9f/5a4f7d959d4feba5e203ff0c31889e74d1ca3153122be4a46dca7d92bf7c/lupa-2.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf3bda96d3fc41237e964a69c23647d50d4e28421111360274d4799832c560e9", size = 2071956, upload-time = "2025-10-24T07:18:14.572Z" }, + { url = "https://files.pythonhosted.org/packages/92/34/2f4f13ca65d01169b1720176aedc4af17bc19ee834598c7292db232cb6dc/lupa-2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a76ead245da54801a81053794aa3975f213221f6542d14ec4b859ee2e7e0323", size = 1057199, upload-time = "2025-10-24T07:18:16.379Z" }, + { url = "https://files.pythonhosted.org/packages/35/2a/5f7d2eebec6993b0dcd428e0184ad71afb06a45ba13e717f6501bfed1da3/lupa-2.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8dd0861741caa20886ddbda0a121d8e52fb9b5bb153d82fa9bba796962bf30e8", size = 1173693, upload-time = "2025-10-24T07:18:18.153Z" }, + { url = "https://files.pythonhosted.org/packages/e4/29/089b4d2f8e34417349af3904bb40bec40b65c8731f45e3fd8d497ca573e5/lupa-2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:239e63948b0b23023f81d9a19a395e768ed3da6a299f84e7963b8f813f6e3f9c", size = 2164394, upload-time = "2025-10-24T07:18:20.403Z" }, + { url = "https://files.pythonhosted.org/packages/f3/1b/79c17b23c921f81468a111cad843b076a17ef4b684c4a8dff32a7969c3f0/lupa-2.6-cp312-cp312-win32.whl", hash = "sha256:325894e1099499e7a6f9c351147661a2011887603c71086d36fe0f964d52d1ce", size = 1420647, upload-time = "2025-10-24T07:18:23.368Z" }, + { url = "https://files.pythonhosted.org/packages/b8/15/5121e68aad3584e26e1425a5c9a79cd898f8a152292059e128c206ee817c/lupa-2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c735a1ce8ee60edb0fe71d665f1e6b7c55c6021f1d340eb8c865952c602cd36f", size = 1688529, upload-time = "2025-10-24T07:18:25.523Z" }, + { url = "https://files.pythonhosted.org/packages/28/1d/21176b682ca5469001199d8b95fa1737e29957a3d185186e7a8b55345f2e/lupa-2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:663a6e58a0f60e7d212017d6678639ac8df0119bc13c2145029dcba084391310", size = 947232, upload-time = "2025-10-24T07:18:27.878Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4c/d327befb684660ca13cf79cd1f1d604331808f9f1b6fb6bf57832f8edf80/lupa-2.6-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:d1f5afda5c20b1f3217a80e9bc1b77037f8a6eb11612fd3ada19065303c8f380", size = 1908625, upload-time = "2025-10-24T07:18:29.944Z" }, + { url = "https://files.pythonhosted.org/packages/66/8e/ad22b0a19454dfd08662237a84c792d6d420d36b061f239e084f29d1a4f3/lupa-2.6-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:26f2b3c085fe76e9119e48c1013c1cccdc1f51585d456858290475aa38e7089e", size = 981057, upload-time = "2025-10-24T07:18:31.553Z" }, + { url = "https://files.pythonhosted.org/packages/5c/48/74859073ab276bd0566c719f9ca0108b0cfc1956ca0d68678d117d47d155/lupa-2.6-cp313-cp313-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:60d2f902c7b96fb8ab98493dcff315e7bb4d0b44dc9dd76eb37de575025d5685", size = 1156227, upload-time = "2025-10-24T07:18:33.981Z" }, + { url = "https://files.pythonhosted.org/packages/09/6c/0e9ded061916877253c2266074060eb71ed99fb21d73c8c114a76725bce2/lupa-2.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a02d25dee3a3250967c36590128d9220ae02f2eda166a24279da0b481519cbff", size = 1035752, upload-time = "2025-10-24T07:18:36.32Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ef/f8c32e454ef9f3fe909f6c7d57a39f950996c37a3deb7b391fec7903dab7/lupa-2.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6eae1ee16b886b8914ff292dbefbf2f48abfbdee94b33a88d1d5475e02423203", size = 2069009, upload-time = "2025-10-24T07:18:38.072Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/15b80c226a5225815a890ee1c11f07968e0aba7a852df41e8ae6fe285063/lupa-2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0edd5073a4ee74ab36f74fe61450148e6044f3952b8d21248581f3c5d1a58be", size = 1056301, upload-time = "2025-10-24T07:18:40.165Z" }, + { url = "https://files.pythonhosted.org/packages/31/14/2086c1425c985acfb30997a67e90c39457122df41324d3c179d6ee2292c6/lupa-2.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0c53ee9f22a8a17e7d4266ad48e86f43771951797042dd51d1494aaa4f5f3f0a", size = 1170673, upload-time = "2025-10-24T07:18:42.426Z" }, + { url = "https://files.pythonhosted.org/packages/10/e5/b216c054cf86576c0191bf9a9f05de6f7e8e07164897d95eea0078dca9b2/lupa-2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:de7c0f157a9064a400d828789191a96da7f4ce889969a588b87ec80de9b14772", size = 2162227, upload-time = "2025-10-24T07:18:46.112Z" }, + { url = "https://files.pythonhosted.org/packages/59/2f/33ecb5bedf4f3bc297ceacb7f016ff951331d352f58e7e791589609ea306/lupa-2.6-cp313-cp313-win32.whl", hash = "sha256:ee9523941ae0a87b5b703417720c5d78f72d2f5bc23883a2ea80a949a3ed9e75", size = 1419558, upload-time = "2025-10-24T07:18:48.371Z" }, + { url = "https://files.pythonhosted.org/packages/f9/b4/55e885834c847ea610e111d87b9ed4768f0afdaeebc00cd46810f25029f6/lupa-2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b1335a5835b0a25ebdbc75cf0bda195e54d133e4d994877ef025e218c2e59db9", size = 1683424, upload-time = "2025-10-24T07:18:50.976Z" }, + { url = "https://files.pythonhosted.org/packages/66/9d/d9427394e54d22a35d1139ef12e845fd700d4872a67a34db32516170b746/lupa-2.6-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:dcb6d0a3264873e1653bc188499f48c1fb4b41a779e315eba45256cfe7bc33c1", size = 953818, upload-time = "2025-10-24T07:18:53.378Z" }, + { url = "https://files.pythonhosted.org/packages/10/41/27bbe81953fb2f9ecfced5d9c99f85b37964cfaf6aa8453bb11283983721/lupa-2.6-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:a37e01f2128f8c36106726cb9d360bac087d58c54b4522b033cc5691c584db18", size = 1915850, upload-time = "2025-10-24T07:18:55.259Z" }, + { url = "https://files.pythonhosted.org/packages/a3/98/f9ff60db84a75ba8725506bbf448fb085bc77868a021998ed2a66d920568/lupa-2.6-cp314-cp314-macosx_11_0_x86_64.whl", hash = "sha256:458bd7e9ff3c150b245b0fcfbb9bd2593d1152ea7f0a7b91c1d185846da033fe", size = 982344, upload-time = "2025-10-24T07:18:57.05Z" }, + { url = "https://files.pythonhosted.org/packages/41/f7/f39e0f1c055c3b887d86b404aaf0ca197b5edfd235a8b81b45b25bac7fc3/lupa-2.6-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:052ee82cac5206a02df77119c325339acbc09f5ce66967f66a2e12a0f3211cad", size = 1156543, upload-time = "2025-10-24T07:18:59.251Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9c/59e6cffa0d672d662ae17bd7ac8ecd2c89c9449dee499e3eb13ca9cd10d9/lupa-2.6-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96594eca3c87dd07938009e95e591e43d554c1dbd0385be03c100367141db5a8", size = 1047974, upload-time = "2025-10-24T07:19:01.449Z" }, + { url = "https://files.pythonhosted.org/packages/23/c6/a04e9cef7c052717fcb28fb63b3824802488f688391895b618e39be0f684/lupa-2.6-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8faddd9d198688c8884091173a088a8e920ecc96cda2ffed576a23574c4b3f6", size = 2073458, upload-time = "2025-10-24T07:19:03.369Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/824173d10f38b51fc77785228f01411b6ca28826ce27404c7c912e0e442c/lupa-2.6-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:daebb3a6b58095c917e76ba727ab37b27477fb926957c825205fbda431552134", size = 1067683, upload-time = "2025-10-24T07:19:06.2Z" }, + { url = "https://files.pythonhosted.org/packages/b6/dc/9692fbcf3c924d9c4ece2d8d2f724451ac2e09af0bd2a782db1cef34e799/lupa-2.6-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:f3154e68972befe0f81564e37d8142b5d5d79931a18309226a04ec92487d4ea3", size = 1171892, upload-time = "2025-10-24T07:19:08.544Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/e318b628d4643c278c96ab3ddea07fc36b075a57383c837f5b11e537ba9d/lupa-2.6-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e4dadf77b9fedc0bfa53417cc28dc2278a26d4cbd95c29f8927ad4d8fe0a7ef9", size = 2166641, upload-time = "2025-10-24T07:19:10.485Z" }, + { url = "https://files.pythonhosted.org/packages/12/f7/a6f9ec2806cf2d50826980cdb4b3cffc7691dc6f95e13cc728846d5cb793/lupa-2.6-cp314-cp314-win32.whl", hash = "sha256:cb34169c6fa3bab3e8ac58ca21b8a7102f6a94b6a5d08d3636312f3f02fafd8f", size = 1456857, upload-time = "2025-10-24T07:19:37.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/de/df71896f25bdc18360fdfa3b802cd7d57d7fede41a0e9724a4625b412c85/lupa-2.6-cp314-cp314-win_amd64.whl", hash = "sha256:b74f944fe46c421e25d0f8692aef1e842192f6f7f68034201382ac440ef9ea67", size = 1731191, upload-time = "2025-10-24T07:19:40.281Z" }, + { url = "https://files.pythonhosted.org/packages/47/3c/a1f23b01c54669465f5f4c4083107d496fbe6fb45998771420e9aadcf145/lupa-2.6-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0e21b716408a21ab65723f8841cf7f2f37a844b7a965eeabb785e27fca4099cf", size = 999343, upload-time = "2025-10-24T07:19:12.519Z" }, + { url = "https://files.pythonhosted.org/packages/c5/6d/501994291cb640bfa2ccf7f554be4e6914afa21c4026bd01bff9ca8aac57/lupa-2.6-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:589db872a141bfff828340079bbdf3e9a31f2689f4ca0d88f97d9e8c2eae6142", size = 2000730, upload-time = "2025-10-24T07:19:14.869Z" }, + { url = "https://files.pythonhosted.org/packages/53/a5/457ffb4f3f20469956c2d4c4842a7675e884efc895b2f23d126d23e126cc/lupa-2.6-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:cd852a91a4a9d4dcbb9a58100f820a75a425703ec3e3f049055f60b8533b7953", size = 1021553, upload-time = "2025-10-24T07:19:17.123Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/36bb5a5d0960f2a5c7c700e0819abb76fd9bf9c1d8a66e5106416d6e9b14/lupa-2.6-cp314-cp314t-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:0334753be028358922415ca97a64a3048e4ed155413fc4eaf87dd0a7e2752983", size = 1133275, upload-time = "2025-10-24T07:19:20.51Z" }, + { url = "https://files.pythonhosted.org/packages/19/86/202ff4429f663013f37d2229f6176ca9f83678a50257d70f61a0a97281bf/lupa-2.6-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:661d895cd38c87658a34780fac54a690ec036ead743e41b74c3fb81a9e65a6aa", size = 1038441, upload-time = "2025-10-24T07:19:22.509Z" }, + { url = "https://files.pythonhosted.org/packages/a7/42/d8125f8e420714e5b52e9c08d88b5329dfb02dcca731b4f21faaee6cc5b5/lupa-2.6-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6aa58454ccc13878cc177c62529a2056be734da16369e451987ff92784994ca7", size = 2058324, upload-time = "2025-10-24T07:19:24.979Z" }, + { url = "https://files.pythonhosted.org/packages/2b/2c/47bf8b84059876e877a339717ddb595a4a7b0e8740bacae78ba527562e1c/lupa-2.6-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1425017264e470c98022bba8cff5bd46d054a827f5df6b80274f9cc71dafd24f", size = 1060250, upload-time = "2025-10-24T07:19:27.262Z" }, + { url = "https://files.pythonhosted.org/packages/c2/06/d88add2b6406ca1bdec99d11a429222837ca6d03bea42ca75afa169a78cb/lupa-2.6-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:224af0532d216e3105f0a127410f12320f7c5f1aa0300bdf9646b8d9afb0048c", size = 1151126, upload-time = "2025-10-24T07:19:29.522Z" }, + { url = "https://files.pythonhosted.org/packages/b4/a0/89e6a024c3b4485b89ef86881c9d55e097e7cb0bdb74efb746f2fa6a9a76/lupa-2.6-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9abb98d5a8fd27c8285302e82199f0e56e463066f88f619d6594a450bf269d80", size = 2153693, upload-time = "2025-10-24T07:19:31.379Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/a0f007dc58fc1bbf51fb85dcc82fcb1f21b8c4261361de7dab0e3d8521ef/lupa-2.6-cp314-cp314t-win32.whl", hash = "sha256:1849efeba7a8f6fb8aa2c13790bee988fd242ae404bd459509640eeea3d1e291", size = 1590104, upload-time = "2025-10-24T07:19:33.514Z" }, + { url = "https://files.pythonhosted.org/packages/7d/5e/db903ce9cf82c48d6b91bf6d63ae4c8d0d17958939a4e04ba6b9f38b8643/lupa-2.6-cp314-cp314t-win_amd64.whl", hash = "sha256:fc1498d1a4fc028bc521c26d0fad4ca00ed63b952e32fb95949bda76a04bad52", size = 1913818, upload-time = "2025-10-24T07:19:36.039Z" }, +] + [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +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/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, + { 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 = "mcp" -version = "1.20.0" +version = "1.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -685,16 +742,18 @@ dependencies = [ { name = "pywin32", marker = "sys_platform == 'win32'" }, { name = "sse-starlette" }, { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/22/fae38092e6c2995c03232635028510d77e7decff31b4ae79dfa0ba99c635/mcp-1.20.0.tar.gz", hash = "sha256:9ccc09eaadbfbcbbdab1c9723cfe2e0d1d9e324d7d3ce7e332ef90b09ed35177", size = 451377, upload-time = "2025-10-30T22:14:53.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/2c/db9ae5ab1fcdd9cd2bcc7ca3b7361b712e30590b64d5151a31563af8f82d/mcp-1.24.0.tar.gz", hash = "sha256:aeaad134664ce56f2721d1abf300666a1e8348563f4d3baff361c3b652448efc", size = 604375, upload-time = "2025-12-12T14:19:38.205Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/00/76fc92f4892d47fecb37131d0e95ea69259f077d84c68f6793a0d96cfe80/mcp-1.20.0-py3-none-any.whl", hash = "sha256:d0dc06f93653f7432ff89f694721c87f79876b6f93741bf628ad1e48f7ac5e5d", size = 173136, upload-time = "2025-10-30T22:14:51.078Z" }, + { url = "https://files.pythonhosted.org/packages/61/0d/5cf14e177c8ae655a2fd9324a6ef657ca4cafd3fc2201c87716055e29641/mcp-1.24.0-py3-none-any.whl", hash = "sha256:db130e103cc50ddc3dffc928382f33ba3eaef0b711f7a87c05e7ded65b1ca062", size = 232896, upload-time = "2025-12-12T14:19:36.14Z" }, ] [[package]] name = "mcpforunityserver" -version = "8.1.6" +version = "8.2.2" source = { editable = "." } dependencies = [ { name = "fastapi" }, @@ -702,7 +761,6 @@ dependencies = [ { name = "httpx" }, { name = "mcp" }, { name = "pydantic" }, - { name = "tomli" }, { name = "uvicorn" }, ] @@ -714,15 +772,14 @@ dev = [ [package.metadata] requires-dist = [ - { name = "fastapi", specifier = ">=0.104.0" }, - { name = "fastmcp", specifier = ">=2.13.0,<2.13.2" }, - { name = "httpx", specifier = ">=0.27.2" }, - { name = "mcp", specifier = ">=1.16.0" }, - { name = "pydantic", specifier = ">=2.12.0" }, - { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, - { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" }, - { name = "tomli", specifier = ">=2.3.0" }, - { name = "uvicorn", specifier = ">=0.35.0" }, + { name = "fastapi", specifier = ">=0.121.1" }, + { name = "fastmcp", specifier = ">=2.13.3" }, + { name = "httpx", specifier = ">=0.28.1" }, + { name = "mcp", specifier = ">=1.21.2" }, + { name = "pydantic", specifier = ">=2.12.5" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9.0.2" }, + { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=1.3.0" }, + { name = "uvicorn", specifier = ">=0.38.0" }, ] provides-extras = ["dev"] @@ -756,6 +813,75 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/97/b9/3161be15bb8e3ad01be8be5a968a9237c3027c5be504362ff800fca3e442/opentelemetry_api-1.39.1.tar.gz", hash = "sha256:fbde8c80e1b937a2c61f20347e91c0c18a1940cecf012d62e65a7caf08967c9c", size = 65767, upload-time = "2025-12-11T13:32:39.182Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cf/df/d3f1ddf4bb4cb50ed9b1139cc7b1c54c34a1e7ce8fd1b9a37c0d1551a6bd/opentelemetry_api-1.39.1-py3-none-any.whl", hash = "sha256:2edd8463432a7f8443edce90972169b195e7d6a05500cd29e6d13898187c9950", size = 66356, upload-time = "2025-12-11T13:32:17.304Z" }, +] + +[[package]] +name = "opentelemetry-exporter-prometheus" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-sdk" }, + { name = "prometheus-client" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/14/39/7dafa6fff210737267bed35a8855b6ac7399b9e582b8cf1f25f842517012/opentelemetry_exporter_prometheus-0.60b1.tar.gz", hash = "sha256:a4011b46906323f71724649d301b4dc188aaa068852e814f4df38cc76eac616b", size = 14976, upload-time = "2025-12-11T13:32:42.944Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/0d/4be6bf5477a3eb3d917d2f17d3c0b6720cd6cb97898444a61d43cc983f5c/opentelemetry_exporter_prometheus-0.60b1-py3-none-any.whl", hash = "sha256:49f59178de4f4590e3cef0b8b95cf6e071aae70e1f060566df5546fad773b8fd", size = 13019, upload-time = "2025-12-11T13:32:23.974Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/41/0f/7e6b713ac117c1f5e4e3300748af699b9902a2e5e34c9cf443dde25a01fa/opentelemetry_instrumentation-0.60b1.tar.gz", hash = "sha256:57ddc7974c6eb35865af0426d1a17132b88b2ed8586897fee187fd5b8944bd6a", size = 31706, upload-time = "2025-12-11T13:36:42.515Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/d2/6788e83c5c86a2690101681aeef27eeb2a6bf22df52d3f263a22cee20915/opentelemetry_instrumentation-0.60b1-py3-none-any.whl", hash = "sha256:04480db952b48fb1ed0073f822f0ee26012b7be7c3eac1a3793122737c78632d", size = 33096, upload-time = "2025-12-11T13:35:33.067Z" }, +] + +[[package]] +name = "opentelemetry-sdk" +version = "1.39.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/fb/c76080c9ba07e1e8235d24cdcc4d125ef7aa3edf23eb4e497c2e50889adc/opentelemetry_sdk-1.39.1.tar.gz", hash = "sha256:cf4d4563caf7bff906c9f7967e2be22d0d6b349b908be0d90fb21c8e9c995cc6", size = 171460, upload-time = "2025-12-11T13:32:49.369Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/98/e91cf858f203d86f4eccdf763dcf01cf03f1dae80c3750f7e635bfa206b6/opentelemetry_sdk-1.39.1-py3-none-any.whl", hash = "sha256:4d5482c478513ecb0a5d938dcc61394e647066e0cc2676bee9f3af3f3f45f01c", size = 132565, upload-time = "2025-12-11T13:32:35.069Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.60b1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/91/df/553f93ed38bf22f4b999d9be9c185adb558982214f33eae539d3b5cd0858/opentelemetry_semantic_conventions-0.60b1.tar.gz", hash = "sha256:87c228b5a0669b748c76d76df6c364c369c28f1c465e50f661e39737e84bc953", size = 137935, upload-time = "2025-12-11T13:32:50.487Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/5e/5958555e09635d09b75de3c4f8b9cae7335ca545d77392ffe7331534c402/opentelemetry_semantic_conventions-0.60b1-py3-none-any.whl", hash = "sha256:9fa8c8b0c110da289809292b0591220d3a7b53c1526a23021e977d68597893fb", size = 219982, upload-time = "2025-12-11T13:32:36.955Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -785,11 +911,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] [[package]] @@ -801,17 +927,26 @@ 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 = "prometheus-client" +version = "0.23.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, +] + [[package]] name = "py-key-value-aio" -version = "0.2.8" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beartype" }, { name = "py-key-value-shared" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/35/65310a4818acec0f87a46e5565e341c5a96fc062a9a03495ad28828ff4d7/py_key_value_aio-0.2.8.tar.gz", hash = "sha256:c0cfbb0bd4e962a3fa1a9fa6db9ba9df812899bd9312fa6368aaea7b26008b36", size = 32853, upload-time = "2025-10-24T13:31:04.688Z" } +sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/5a/e56747d87a97ad2aff0f3700d77f186f0704c90c2da03bfed9e113dae284/py_key_value_aio-0.2.8-py3-none-any.whl", hash = "sha256:561565547ce8162128fd2bd0b9d70ce04a5f4586da8500cce79a54dfac78c46a", size = 69200, upload-time = "2025-10-24T13:31:03.81Z" }, + { url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342, upload-time = "2025-11-17T16:50:03.801Z" }, ] [package.optional-dependencies] @@ -825,18 +960,21 @@ keyring = [ memory = [ { name = "cachetools" }, ] +redis = [ + { name = "redis" }, +] [[package]] name = "py-key-value-shared" -version = "0.2.8" +version = "0.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "beartype" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/26/79/05a1f9280cfa0709479319cbfd2b1c5beb23d5034624f548c83fb65b0b61/py_key_value_shared-0.2.8.tar.gz", hash = "sha256:703b4d3c61af124f0d528ba85995c3c8d78f8bd3d2b217377bd3278598070cc1", size = 8216, upload-time = "2025-10-24T13:31:03.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/7a/1726ceaa3343874f322dd83c9ec376ad81f533df8422b8b1e1233a59f8ce/py_key_value_shared-0.2.8-py3-none-any.whl", hash = "sha256:aff1bbfd46d065b2d67897d298642e80e5349eae588c6d11b48452b46b8d46ba", size = 14586, upload-time = "2025-10-24T13:31:02.838Z" }, + { url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560, upload-time = "2025-11-17T16:50:05.954Z" }, ] [[package]] @@ -850,7 +988,7 @@ wheels = [ [[package]] name = "pydantic" -version = "2.12.0" +version = "2.12.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-types" }, @@ -858,9 +996,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/da/b8a7ee04378a53f6fefefc0c5e05570a3ebfdfa0523a878bcd3b475683ee/pydantic-2.12.0.tar.gz", hash = "sha256:c1a077e6270dbfb37bfd8b498b3981e2bb18f68103720e51fa6c306a5a9af563", size = 814760, upload-time = "2025-10-07T15:58:03.467Z" } +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/9d/d5c855424e2e5b6b626fbc6ec514d8e655a600377ce283008b115abb7445/pydantic-2.12.0-py3-none-any.whl", hash = "sha256:f6a1da352d42790537e95e83a8bdfb91c7efbae63ffd0b86fa823899e807116f", size = 459730, upload-time = "2025-10-07T15:58:01.576Z" }, + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, ] [package.optional-dependencies] @@ -870,134 +1008,145 @@ email = [ [[package]] name = "pydantic-core" -version = "2.41.1" +version = "2.41.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/14/12b4a0d2b0b10d8e1d9a24ad94e7bbb43335eaf29c0c4e57860e8a30734a/pydantic_core-2.41.1.tar.gz", hash = "sha256:1ad375859a6d8c356b7704ec0f547a58e82ee80bb41baa811ad710e124bc8f2f", size = 454870, upload-time = "2025-10-07T10:50:45.974Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2c/a5c4640dc7132540109f67fe83b566fbc7512ccf2a068cfa22a243df70c7/pydantic_core-2.41.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e63036298322e9aea1c8b7c0a6c1204d615dbf6ec0668ce5b83ff27f07404a61", size = 2113814, upload-time = "2025-10-06T21:09:50.892Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e7/a8694c3454a57842095d69c7a4ab3cf81c3c7b590f052738eabfdfc2e234/pydantic_core-2.41.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:241299ca91fc77ef64f11ed909d2d9220a01834e8e6f8de61275c4dd16b7c936", size = 1916660, upload-time = "2025-10-06T21:09:52.783Z" }, - { url = "https://files.pythonhosted.org/packages/9c/58/29f12e65b19c1877a0269eb4f23c5d2267eded6120a7d6762501ab843dc9/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ab7e594a2a5c24ab8013a7dc8cfe5f2260e80e490685814122081705c2cf2b0", size = 1975071, upload-time = "2025-10-06T21:09:54.009Z" }, - { url = "https://files.pythonhosted.org/packages/98/26/4e677f2b7ec3fbdd10be6b586a82a814c8ebe3e474024c8df2d4260e564e/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b054ef1a78519cb934b58e9c90c09e93b837c935dcd907b891f2b265b129eb6e", size = 2067271, upload-time = "2025-10-06T21:09:55.175Z" }, - { url = "https://files.pythonhosted.org/packages/29/50/50614bd906089904d7ca1be3b9ecf08c00a327143d48f1decfdc21b3c302/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f2ab7d10d0ab2ed6da54c757233eb0f48ebfb4f86e9b88ccecb3f92bbd61a538", size = 2253207, upload-time = "2025-10-06T21:09:56.709Z" }, - { url = "https://files.pythonhosted.org/packages/ea/58/b1e640b4ca559273cca7c28e0fe8891d5d8e9a600f5ab4882670ec107549/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2757606b7948bb853a27e4040820306eaa0ccb9e8f9f8a0fa40cb674e170f350", size = 2375052, upload-time = "2025-10-06T21:09:57.97Z" }, - { url = "https://files.pythonhosted.org/packages/53/25/cd47df3bfb24350e03835f0950288d1054f1cc9a8023401dabe6d4ff2834/pydantic_core-2.41.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cec0e75eb61f606bad0a32f2be87507087514e26e8c73db6cbdb8371ccd27917", size = 2076834, upload-time = "2025-10-06T21:09:59.58Z" }, - { url = "https://files.pythonhosted.org/packages/ec/b4/71b2c77e5df527fbbc1a03e72c3fd96c44cd10d4241a81befef8c12b9fc4/pydantic_core-2.41.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0234236514f44a5bf552105cfe2543a12f48203397d9d0f866affa569345a5b5", size = 2195374, upload-time = "2025-10-06T21:10:01.18Z" }, - { url = "https://files.pythonhosted.org/packages/aa/08/4b8a50733005865efde284fec45da75fe16a258f706e16323c5ace4004eb/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1b974e41adfbb4ebb0f65fc4ca951347b17463d60893ba7d5f7b9bb087c83897", size = 2156060, upload-time = "2025-10-06T21:10:02.74Z" }, - { url = "https://files.pythonhosted.org/packages/83/c3/1037cb603ef2130c210150a51b1710d86825b5c28df54a55750099f91196/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:248dafb3204136113c383e91a4d815269f51562b6659b756cf3df14eefc7d0bb", size = 2331640, upload-time = "2025-10-06T21:10:04.39Z" }, - { url = "https://files.pythonhosted.org/packages/56/4c/52d111869610e6b1a46e1f1035abcdc94d0655587e39104433a290e9f377/pydantic_core-2.41.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:678f9d76a91d6bcedd7568bbf6beb77ae8447f85d1aeebaab7e2f0829cfc3a13", size = 2329844, upload-time = "2025-10-06T21:10:05.68Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/4b435f0b52ab543967761aca66b84ad3f0026e491e57de47693d15d0a8db/pydantic_core-2.41.1-cp310-cp310-win32.whl", hash = "sha256:dff5bee1d21ee58277900692a641925d2dddfde65182c972569b1a276d2ac8fb", size = 1991289, upload-time = "2025-10-06T21:10:07.199Z" }, - { url = "https://files.pythonhosted.org/packages/88/52/31b4deafc1d3cb96d0e7c0af70f0dc05454982d135d07f5117e6336153e8/pydantic_core-2.41.1-cp310-cp310-win_amd64.whl", hash = "sha256:5042da12e5d97d215f91567110fdfa2e2595a25f17c19b9ff024f31c34f9b53e", size = 2027747, upload-time = "2025-10-06T21:10:08.503Z" }, - { url = "https://files.pythonhosted.org/packages/f6/a9/ec440f02e57beabdfd804725ef1e38ac1ba00c49854d298447562e119513/pydantic_core-2.41.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4f276a6134fe1fc1daa692642a3eaa2b7b858599c49a7610816388f5e37566a1", size = 2111456, upload-time = "2025-10-06T21:10:09.824Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f9/6bc15bacfd8dcfc073a1820a564516d9c12a435a9a332d4cbbfd48828ddd/pydantic_core-2.41.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07588570a805296ece009c59d9a679dc08fab72fb337365afb4f3a14cfbfc176", size = 1915012, upload-time = "2025-10-06T21:10:11.599Z" }, - { url = "https://files.pythonhosted.org/packages/38/8a/d9edcdcdfe80bade17bed424284427c08bea892aaec11438fa52eaeaf79c/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28527e4b53400cd60ffbd9812ccb2b5135d042129716d71afd7e45bf42b855c0", size = 1973762, upload-time = "2025-10-06T21:10:13.154Z" }, - { url = "https://files.pythonhosted.org/packages/d5/b3/ff225c6d49fba4279de04677c1c876fc3dc6562fd0c53e9bfd66f58c51a8/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:46a1c935c9228bad738c8a41de06478770927baedf581d172494ab36a6b96575", size = 2065386, upload-time = "2025-10-06T21:10:14.436Z" }, - { url = "https://files.pythonhosted.org/packages/47/ba/183e8c0be4321314af3fd1ae6bfc7eafdd7a49bdea5da81c56044a207316/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:447ddf56e2b7d28d200d3e9eafa936fe40485744b5a824b67039937580b3cb20", size = 2252317, upload-time = "2025-10-06T21:10:15.719Z" }, - { url = "https://files.pythonhosted.org/packages/57/c5/aab61e94fd02f45c65f1f8c9ec38bb3b33fbf001a1837c74870e97462572/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:63892ead40c1160ac860b5debcc95c95c5a0035e543a8b5a4eac70dd22e995f4", size = 2373405, upload-time = "2025-10-06T21:10:17.017Z" }, - { url = "https://files.pythonhosted.org/packages/e5/4f/3aaa3bd1ea420a15acc42d7d3ccb3b0bbc5444ae2f9dbc1959f8173e16b8/pydantic_core-2.41.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4a9543ca355e6df8fbe9c83e9faab707701e9103ae857ecb40f1c0cf8b0e94d", size = 2073794, upload-time = "2025-10-06T21:10:18.383Z" }, - { url = "https://files.pythonhosted.org/packages/58/bd/e3975cdebe03ec080ef881648de316c73f2a6be95c14fc4efb2f7bdd0d41/pydantic_core-2.41.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f2611bdb694116c31e551ed82e20e39a90bea9b7ad9e54aaf2d045ad621aa7a1", size = 2194430, upload-time = "2025-10-06T21:10:19.638Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b8/6b7e7217f147d3b3105b57fb1caec3c4f667581affdfaab6d1d277e1f749/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fecc130893a9b5f7bfe230be1bb8c61fe66a19db8ab704f808cb25a82aad0bc9", size = 2154611, upload-time = "2025-10-06T21:10:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7b/239c2fe76bd8b7eef9ae2140d737368a3c6fea4fd27f8f6b4cde6baa3ce9/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:1e2df5f8344c99b6ea5219f00fdc8950b8e6f2c422fbc1cc122ec8641fac85a1", size = 2329809, upload-time = "2025-10-06T21:10:22.678Z" }, - { url = "https://files.pythonhosted.org/packages/bd/2e/77a821a67ff0786f2f14856d6bd1348992f695ee90136a145d7a445c1ff6/pydantic_core-2.41.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:35291331e9d8ed94c257bab6be1cb3a380b5eee570a2784bffc055e18040a2ea", size = 2327907, upload-time = "2025-10-06T21:10:24.447Z" }, - { url = "https://files.pythonhosted.org/packages/fd/9a/b54512bb9df7f64c586b369328c30481229b70ca6a5fcbb90b715e15facf/pydantic_core-2.41.1-cp311-cp311-win32.whl", hash = "sha256:2876a095292668d753f1a868c4a57c4ac9f6acbd8edda8debe4218d5848cf42f", size = 1989964, upload-time = "2025-10-06T21:10:25.676Z" }, - { url = "https://files.pythonhosted.org/packages/9d/72/63c9a4f1a5c950e65dd522d7dd67f167681f9d4f6ece3b80085a0329f08f/pydantic_core-2.41.1-cp311-cp311-win_amd64.whl", hash = "sha256:b92d6c628e9a338846a28dfe3fcdc1a3279388624597898b105e078cdfc59298", size = 2025158, upload-time = "2025-10-06T21:10:27.522Z" }, - { url = "https://files.pythonhosted.org/packages/d8/16/4e2706184209f61b50c231529257c12eb6bd9eb36e99ea1272e4815d2200/pydantic_core-2.41.1-cp311-cp311-win_arm64.whl", hash = "sha256:7d82ae99409eb69d507a89835488fb657faa03ff9968a9379567b0d2e2e56bc5", size = 1972297, upload-time = "2025-10-06T21:10:28.814Z" }, - { url = "https://files.pythonhosted.org/packages/ee/bc/5f520319ee1c9e25010412fac4154a72e0a40d0a19eb00281b1f200c0947/pydantic_core-2.41.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:db2f82c0ccbce8f021ad304ce35cbe02aa2f95f215cac388eed542b03b4d5eb4", size = 2099300, upload-time = "2025-10-06T21:10:30.463Z" }, - { url = "https://files.pythonhosted.org/packages/31/14/010cd64c5c3814fb6064786837ec12604be0dd46df3327cf8474e38abbbd/pydantic_core-2.41.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47694a31c710ced9205d5f1e7e8af3ca57cbb8a503d98cb9e33e27c97a501601", size = 1910179, upload-time = "2025-10-06T21:10:31.782Z" }, - { url = "https://files.pythonhosted.org/packages/8e/2e/23fc2a8a93efad52df302fdade0a60f471ecc0c7aac889801ac24b4c07d6/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e9decce94daf47baf9e9d392f5f2557e783085f7c5e522011545d9d6858e00", size = 1957225, upload-time = "2025-10-06T21:10:33.11Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b6/6db08b2725b2432b9390844852e11d320281e5cea8a859c52c68001975fa/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ab0adafdf2b89c8b84f847780a119437a0931eca469f7b44d356f2b426dd9741", size = 2053315, upload-time = "2025-10-06T21:10:34.87Z" }, - { url = "https://files.pythonhosted.org/packages/61/d9/4de44600f2d4514b44f3f3aeeda2e14931214b6b5bf52479339e801ce748/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5da98cc81873f39fd56882e1569c4677940fbc12bce6213fad1ead784192d7c8", size = 2224298, upload-time = "2025-10-06T21:10:36.233Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ae/dbe51187a7f35fc21b283c5250571a94e36373eb557c1cba9f29a9806dcf/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:209910e88afb01fd0fd403947b809ba8dba0e08a095e1f703294fda0a8fdca51", size = 2351797, upload-time = "2025-10-06T21:10:37.601Z" }, - { url = "https://files.pythonhosted.org/packages/b5/a7/975585147457c2e9fb951c7c8dab56deeb6aa313f3aa72c2fc0df3f74a49/pydantic_core-2.41.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:365109d1165d78d98e33c5bfd815a9b5d7d070f578caefaabcc5771825b4ecb5", size = 2074921, upload-time = "2025-10-06T21:10:38.927Z" }, - { url = "https://files.pythonhosted.org/packages/62/37/ea94d1d0c01dec1b7d236c7cec9103baab0021f42500975de3d42522104b/pydantic_core-2.41.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:706abf21e60a2857acdb09502bc853ee5bce732955e7b723b10311114f033115", size = 2187767, upload-time = "2025-10-06T21:10:40.651Z" }, - { url = "https://files.pythonhosted.org/packages/d3/fe/694cf9fdd3a777a618c3afd210dba7b414cb8a72b1bd29b199c2e5765fee/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bf0bd5417acf7f6a7ec3b53f2109f587be176cb35f9cf016da87e6017437a72d", size = 2136062, upload-time = "2025-10-06T21:10:42.09Z" }, - { url = "https://files.pythonhosted.org/packages/0f/ae/174aeabd89916fbd2988cc37b81a59e1186e952afd2a7ed92018c22f31ca/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:2e71b1c6ceb9c78424ae9f63a07292fb769fb890a4e7efca5554c47f33a60ea5", size = 2317819, upload-time = "2025-10-06T21:10:43.974Z" }, - { url = "https://files.pythonhosted.org/packages/65/e8/e9aecafaebf53fc456314f72886068725d6fba66f11b013532dc21259343/pydantic_core-2.41.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:80745b9770b4a38c25015b517451c817799bfb9d6499b0d13d8227ec941cb513", size = 2312267, upload-time = "2025-10-06T21:10:45.34Z" }, - { url = "https://files.pythonhosted.org/packages/35/2f/1c2e71d2a052f9bb2f2df5a6a05464a0eb800f9e8d9dd800202fe31219e1/pydantic_core-2.41.1-cp312-cp312-win32.whl", hash = "sha256:83b64d70520e7890453f1aa21d66fda44e7b35f1cfea95adf7b4289a51e2b479", size = 1990927, upload-time = "2025-10-06T21:10:46.738Z" }, - { url = "https://files.pythonhosted.org/packages/b1/78/562998301ff2588b9c6dcc5cb21f52fa919d6e1decc75a35055feb973594/pydantic_core-2.41.1-cp312-cp312-win_amd64.whl", hash = "sha256:377defd66ee2003748ee93c52bcef2d14fde48fe28a0b156f88c3dbf9bc49a50", size = 2034703, upload-time = "2025-10-06T21:10:48.524Z" }, - { url = "https://files.pythonhosted.org/packages/b2/53/d95699ce5a5cdb44bb470bd818b848b9beadf51459fd4ea06667e8ede862/pydantic_core-2.41.1-cp312-cp312-win_arm64.whl", hash = "sha256:c95caff279d49c1d6cdfe2996e6c2ad712571d3b9caaa209a404426c326c4bde", size = 1972719, upload-time = "2025-10-06T21:10:50.256Z" }, - { url = "https://files.pythonhosted.org/packages/27/8a/6d54198536a90a37807d31a156642aae7a8e1263ed9fe6fc6245defe9332/pydantic_core-2.41.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70e790fce5f05204ef4403159857bfcd587779da78627b0babb3654f75361ebf", size = 2105825, upload-time = "2025-10-06T21:10:51.719Z" }, - { url = "https://files.pythonhosted.org/packages/4f/2e/4784fd7b22ac9c8439db25bf98ffed6853d01e7e560a346e8af821776ccc/pydantic_core-2.41.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9cebf1ca35f10930612d60bd0f78adfacee824c30a880e3534ba02c207cceceb", size = 1910126, upload-time = "2025-10-06T21:10:53.145Z" }, - { url = "https://files.pythonhosted.org/packages/f3/92/31eb0748059ba5bd0aa708fb4bab9fcb211461ddcf9e90702a6542f22d0d/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:170406a37a5bc82c22c3274616bf6f17cc7df9c4a0a0a50449e559cb755db669", size = 1961472, upload-time = "2025-10-06T21:10:55.754Z" }, - { url = "https://files.pythonhosted.org/packages/ab/91/946527792275b5c4c7dde4cfa3e81241bf6900e9fee74fb1ba43e0c0f1ab/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12d4257fc9187a0ccd41b8b327d6a4e57281ab75e11dda66a9148ef2e1fb712f", size = 2063230, upload-time = "2025-10-06T21:10:57.179Z" }, - { url = "https://files.pythonhosted.org/packages/31/5d/a35c5d7b414e5c0749f1d9f0d159ee2ef4bab313f499692896b918014ee3/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a75a33b4db105dd1c8d57839e17ee12db8d5ad18209e792fa325dbb4baeb00f4", size = 2229469, upload-time = "2025-10-06T21:10:59.409Z" }, - { url = "https://files.pythonhosted.org/packages/21/4d/8713737c689afa57ecfefe38db78259d4484c97aa494979e6a9d19662584/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:08a589f850803a74e0fcb16a72081cafb0d72a3cdda500106942b07e76b7bf62", size = 2347986, upload-time = "2025-10-06T21:11:00.847Z" }, - { url = "https://files.pythonhosted.org/packages/f6/ec/929f9a3a5ed5cda767081494bacd32f783e707a690ce6eeb5e0730ec4986/pydantic_core-2.41.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a97939d6ea44763c456bd8a617ceada2c9b96bb5b8ab3dfa0d0827df7619014", size = 2072216, upload-time = "2025-10-06T21:11:02.43Z" }, - { url = "https://files.pythonhosted.org/packages/26/55/a33f459d4f9cc8786d9db42795dbecc84fa724b290d7d71ddc3d7155d46a/pydantic_core-2.41.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d2ae423c65c556f09569524b80ffd11babff61f33055ef9773d7c9fabc11ed8d", size = 2193047, upload-time = "2025-10-06T21:11:03.787Z" }, - { url = "https://files.pythonhosted.org/packages/77/af/d5c6959f8b089f2185760a2779079e3c2c411bfc70ea6111f58367851629/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:4dc703015fbf8764d6a8001c327a87f1823b7328d40b47ce6000c65918ad2b4f", size = 2140613, upload-time = "2025-10-06T21:11:05.607Z" }, - { url = "https://files.pythonhosted.org/packages/58/e5/2c19bd2a14bffe7fabcf00efbfbd3ac430aaec5271b504a938ff019ac7be/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:968e4ffdfd35698a5fe659e5e44c508b53664870a8e61c8f9d24d3d145d30257", size = 2327641, upload-time = "2025-10-06T21:11:07.143Z" }, - { url = "https://files.pythonhosted.org/packages/93/ef/e0870ccda798c54e6b100aff3c4d49df5458fd64217e860cb9c3b0a403f4/pydantic_core-2.41.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:fff2b76c8e172d34771cd4d4f0ade08072385310f214f823b5a6ad4006890d32", size = 2318229, upload-time = "2025-10-06T21:11:08.73Z" }, - { url = "https://files.pythonhosted.org/packages/b1/4b/c3b991d95f5deb24d0bd52e47bcf716098fa1afe0ce2d4bd3125b38566ba/pydantic_core-2.41.1-cp313-cp313-win32.whl", hash = "sha256:a38a5263185407ceb599f2f035faf4589d57e73c7146d64f10577f6449e8171d", size = 1997911, upload-time = "2025-10-06T21:11:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/a7/ce/5c316fd62e01f8d6be1b7ee6b54273214e871772997dc2c95e204997a055/pydantic_core-2.41.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42ae7fd6760782c975897e1fdc810f483b021b32245b0105d40f6e7a3803e4b", size = 2034301, upload-time = "2025-10-06T21:11:12.113Z" }, - { url = "https://files.pythonhosted.org/packages/29/41/902640cfd6a6523194123e2c3373c60f19006447f2fb06f76de4e8466c5b/pydantic_core-2.41.1-cp313-cp313-win_arm64.whl", hash = "sha256:ad4111acc63b7384e205c27a2f15e23ac0ee21a9d77ad6f2e9cb516ec90965fb", size = 1977238, upload-time = "2025-10-06T21:11:14.1Z" }, - { url = "https://files.pythonhosted.org/packages/04/04/28b040e88c1b89d851278478842f0bdf39c7a05da9e850333c6c8cbe7dfa/pydantic_core-2.41.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:440d0df7415b50084a4ba9d870480c16c5f67c0d1d4d5119e3f70925533a0edc", size = 1875626, upload-time = "2025-10-06T21:11:15.69Z" }, - { url = "https://files.pythonhosted.org/packages/d6/58/b41dd3087505220bb58bc81be8c3e8cbc037f5710cd3c838f44f90bdd704/pydantic_core-2.41.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71eaa38d342099405dae6484216dcf1e8e4b0bebd9b44a4e08c9b43db6a2ab67", size = 2045708, upload-time = "2025-10-06T21:11:17.258Z" }, - { url = "https://files.pythonhosted.org/packages/d7/b8/760f23754e40bf6c65b94a69b22c394c24058a0ef7e2aa471d2e39219c1a/pydantic_core-2.41.1-cp313-cp313t-win_amd64.whl", hash = "sha256:555ecf7e50f1161d3f693bc49f23c82cf6cdeafc71fa37a06120772a09a38795", size = 1997171, upload-time = "2025-10-06T21:11:18.822Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/cec246429ddfa2778d2d6301eca5362194dc8749ecb19e621f2f65b5090f/pydantic_core-2.41.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:05226894a26f6f27e1deb735d7308f74ef5fa3a6de3e0135bb66cdcaee88f64b", size = 2107836, upload-time = "2025-10-06T21:11:20.432Z" }, - { url = "https://files.pythonhosted.org/packages/20/39/baba47f8d8b87081302498e610aefc37142ce6a1cc98b2ab6b931a162562/pydantic_core-2.41.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:85ff7911c6c3e2fd8d3779c50925f6406d770ea58ea6dde9c230d35b52b16b4a", size = 1904449, upload-time = "2025-10-06T21:11:22.185Z" }, - { url = "https://files.pythonhosted.org/packages/50/32/9a3d87cae2c75a5178334b10358d631bd094b916a00a5993382222dbfd92/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47f1f642a205687d59b52dc1a9a607f45e588f5a2e9eeae05edd80c7a8c47674", size = 1961750, upload-time = "2025-10-06T21:11:24.348Z" }, - { url = "https://files.pythonhosted.org/packages/27/42/a96c9d793a04cf2a9773bff98003bb154087b94f5530a2ce6063ecfec583/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df11c24e138876ace5ec6043e5cae925e34cf38af1a1b3d63589e8f7b5f5cdc4", size = 2063305, upload-time = "2025-10-06T21:11:26.556Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8d/028c4b7d157a005b1f52c086e2d4b0067886b213c86220c1153398dbdf8f/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f0bf7f5c8f7bf345c527e8a0d72d6b26eda99c1227b0c34e7e59e181260de31", size = 2228959, upload-time = "2025-10-06T21:11:28.426Z" }, - { url = "https://files.pythonhosted.org/packages/08/f7/ee64cda8fcc9ca3f4716e6357144f9ee71166775df582a1b6b738bf6da57/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82b887a711d341c2c47352375d73b029418f55b20bd7815446d175a70effa706", size = 2345421, upload-time = "2025-10-06T21:11:30.226Z" }, - { url = "https://files.pythonhosted.org/packages/13/c0/e8ec05f0f5ee7a3656973ad9cd3bc73204af99f6512c1a4562f6fb4b3f7d/pydantic_core-2.41.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5f1d5d6bbba484bdf220c72d8ecd0be460f4bd4c5e534a541bb2cd57589fb8b", size = 2065288, upload-time = "2025-10-06T21:11:32.019Z" }, - { url = "https://files.pythonhosted.org/packages/0a/25/d77a73ff24e2e4fcea64472f5e39b0402d836da9b08b5361a734d0153023/pydantic_core-2.41.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2bf1917385ebe0f968dc5c6ab1375886d56992b93ddfe6bf52bff575d03662be", size = 2189759, upload-time = "2025-10-06T21:11:33.753Z" }, - { url = "https://files.pythonhosted.org/packages/66/45/4a4ebaaae12a740552278d06fe71418c0f2869537a369a89c0e6723b341d/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4f94f3ab188f44b9a73f7295663f3ecb8f2e2dd03a69c8f2ead50d37785ecb04", size = 2140747, upload-time = "2025-10-06T21:11:35.781Z" }, - { url = "https://files.pythonhosted.org/packages/da/6d/b727ce1022f143194a36593243ff244ed5a1eb3c9122296bf7e716aa37ba/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:3925446673641d37c30bd84a9d597e49f72eacee8b43322c8999fa17d5ae5bc4", size = 2327416, upload-time = "2025-10-06T21:11:37.75Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8c/02df9d8506c427787059f87c6c7253435c6895e12472a652d9616ee0fc95/pydantic_core-2.41.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:49bd51cc27adb980c7b97357ae036ce9b3c4d0bb406e84fbe16fb2d368b602a8", size = 2318138, upload-time = "2025-10-06T21:11:39.463Z" }, - { url = "https://files.pythonhosted.org/packages/98/67/0cf429a7d6802536941f430e6e3243f6d4b68f41eeea4b242372f1901794/pydantic_core-2.41.1-cp314-cp314-win32.whl", hash = "sha256:a31ca0cd0e4d12ea0df0077df2d487fc3eb9d7f96bbb13c3c5b88dcc21d05159", size = 1998429, upload-time = "2025-10-06T21:11:41.989Z" }, - { url = "https://files.pythonhosted.org/packages/38/60/742fef93de5d085022d2302a6317a2b34dbfe15258e9396a535c8a100ae7/pydantic_core-2.41.1-cp314-cp314-win_amd64.whl", hash = "sha256:1b5c4374a152e10a22175d7790e644fbd8ff58418890e07e2073ff9d4414efae", size = 2028870, upload-time = "2025-10-06T21:11:43.66Z" }, - { url = "https://files.pythonhosted.org/packages/31/38/cdd8ccb8555ef7720bd7715899bd6cfbe3c29198332710e1b61b8f5dd8b8/pydantic_core-2.41.1-cp314-cp314-win_arm64.whl", hash = "sha256:4fee76d757639b493eb600fba668f1e17475af34c17dd61db7a47e824d464ca9", size = 1974275, upload-time = "2025-10-06T21:11:45.476Z" }, - { url = "https://files.pythonhosted.org/packages/e7/7e/8ac10ccb047dc0221aa2530ec3c7c05ab4656d4d4bd984ee85da7f3d5525/pydantic_core-2.41.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f9b9c968cfe5cd576fdd7361f47f27adeb120517e637d1b189eea1c3ece573f4", size = 1875124, upload-time = "2025-10-06T21:11:47.591Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e4/7d9791efeb9c7d97e7268f8d20e0da24d03438a7fa7163ab58f1073ba968/pydantic_core-2.41.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ebc7ab67b856384aba09ed74e3e977dded40e693de18a4f197c67d0d4e6d8e", size = 2043075, upload-time = "2025-10-06T21:11:49.542Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c3/3f6e6b2342ac11ac8cd5cb56e24c7b14afa27c010e82a765ffa5f771884a/pydantic_core-2.41.1-cp314-cp314t-win_amd64.whl", hash = "sha256:8ae0dc57b62a762985bc7fbf636be3412394acc0ddb4ade07fe104230f1b9762", size = 1995341, upload-time = "2025-10-06T21:11:51.497Z" }, - { url = "https://files.pythonhosted.org/packages/16/89/d0afad37ba25f5801735af1472e650b86baad9fe807a42076508e4824a2a/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:68f2251559b8efa99041bb63571ec7cdd2d715ba74cc82b3bc9eff824ebc8bf0", size = 2124001, upload-time = "2025-10-07T10:49:54.369Z" }, - { url = "https://files.pythonhosted.org/packages/8e/c4/08609134b34520568ddebb084d9ed0a2a3f5f52b45739e6e22cb3a7112eb/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:c7bc140c596097cb53b30546ca257dbe3f19282283190b1b5142928e5d5d3a20", size = 1941841, upload-time = "2025-10-07T10:49:56.248Z" }, - { url = "https://files.pythonhosted.org/packages/2a/43/94a4877094e5fe19a3f37e7e817772263e2c573c94f1e3fa2b1eee56ef3b/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2896510fce8f4725ec518f8b9d7f015a00db249d2fd40788f442af303480063d", size = 1961129, upload-time = "2025-10-07T10:49:58.298Z" }, - { url = "https://files.pythonhosted.org/packages/a2/30/23a224d7e25260eb5f69783a63667453037e07eb91ff0e62dabaadd47128/pydantic_core-2.41.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ced20e62cfa0f496ba68fa5d6c7ee71114ea67e2a5da3114d6450d7f4683572a", size = 2148770, upload-time = "2025-10-07T10:49:59.959Z" }, - { url = "https://files.pythonhosted.org/packages/2b/3e/a51c5f5d37b9288ba30683d6e96f10fa8f1defad1623ff09f1020973b577/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b04fa9ed049461a7398138c604b00550bc89e3e1151d84b81ad6dc93e39c4c06", size = 2115344, upload-time = "2025-10-07T10:50:02.466Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bd/389504c9e0600ef4502cd5238396b527afe6ef8981a6a15cd1814fc7b434/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:b3b7d9cfbfdc43c80a16638c6dc2768e3956e73031fca64e8e1a3ae744d1faeb", size = 1927994, upload-time = "2025-10-07T10:50:04.379Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9c/5111c6b128861cb792a4c082677e90dac4f2e090bb2e2fe06aa5b2d39027/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eec83fc6abef04c7f9bec616e2d76ee9a6a4ae2a359b10c21d0f680e24a247ca", size = 1959394, upload-time = "2025-10-07T10:50:06.335Z" }, - { url = "https://files.pythonhosted.org/packages/14/3f/cfec8b9a0c48ce5d64409ec5e1903cb0b7363da38f14b41de2fcb3712700/pydantic_core-2.41.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6771a2d9f83c4038dfad5970a3eef215940682b2175e32bcc817bdc639019b28", size = 2147365, upload-time = "2025-10-07T10:50:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/d4/31/f403d7ca8352e3e4df352ccacd200f5f7f7fe81cef8e458515f015091625/pydantic_core-2.41.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fabcbdb12de6eada8d6e9a759097adb3c15440fafc675b3e94ae5c9cb8d678a0", size = 2114268, upload-time = "2025-10-07T10:50:10.257Z" }, - { url = "https://files.pythonhosted.org/packages/6e/b5/334473b6d2810df84db67f03d4f666acacfc538512c2d2a254074fee0889/pydantic_core-2.41.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e97ccfaf0aaf67d55de5085b0ed0d994f57747d9d03f2de5cc9847ca737b08", size = 1935786, upload-time = "2025-10-07T10:50:12.333Z" }, - { url = "https://files.pythonhosted.org/packages/ea/5e/45513e4dc621f47397cfa5fef12ba8fa5e8b1c4c07f2ff2a5fef8ff81b25/pydantic_core-2.41.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34df1fe8fea5d332484a763702e8b6a54048a9d4fe6ccf41e34a128238e01f52", size = 1971995, upload-time = "2025-10-07T10:50:14.071Z" }, - { url = "https://files.pythonhosted.org/packages/22/e3/f1797c168e5f52b973bed1c585e99827a22d5e579d1ed57d51bc15b14633/pydantic_core-2.41.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:421b5595f845842fc093f7250e24ee395f54ca62d494fdde96f43ecf9228ae01", size = 2191264, upload-time = "2025-10-07T10:50:15.788Z" }, - { url = "https://files.pythonhosted.org/packages/bb/e1/24ef4c3b4ab91c21c3a09a966c7d2cffe101058a7bfe5cc8b2c7c7d574e2/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:dce8b22663c134583aaad24827863306a933f576c79da450be3984924e2031d1", size = 2152430, upload-time = "2025-10-07T10:50:18.018Z" }, - { url = "https://files.pythonhosted.org/packages/35/74/70c1e225d67f7ef3fdba02c506d9011efaf734020914920b2aa3d1a45e61/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:300a9c162fea9906cc5c103893ca2602afd84f0ec90d3be36f4cc360125d22e1", size = 2324691, upload-time = "2025-10-07T10:50:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/c8/bf/dd4d21037c8bef0d8cce90a86a3f2dcb011c30086db2a10113c3eea23eba/pydantic_core-2.41.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:e019167628f6e6161ae7ab9fb70f6d076a0bf0d55aa9b20833f86a320c70dd65", size = 2324493, upload-time = "2025-10-07T10:50:21.568Z" }, - { url = "https://files.pythonhosted.org/packages/7e/78/3093b334e9c9796c8236a4701cd2ddef1c56fb0928fe282a10c797644380/pydantic_core-2.41.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:13ab9cc2de6f9d4ab645a050ae5aee61a2424ac4d3a16ba23d4c2027705e0301", size = 2146156, upload-time = "2025-10-07T10:50:23.475Z" }, - { url = "https://files.pythonhosted.org/packages/e6/6c/fa3e45c2b054a1e627a89a364917f12cbe3abc3e91b9004edaae16e7b3c5/pydantic_core-2.41.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:af2385d3f98243fb733862f806c5bb9122e5fba05b373e3af40e3c82d711cef1", size = 2112094, upload-time = "2025-10-07T10:50:25.513Z" }, - { url = "https://files.pythonhosted.org/packages/e5/17/7eebc38b4658cc8e6902d0befc26388e4c2a5f2e179c561eeb43e1922c7b/pydantic_core-2.41.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6550617a0c2115be56f90c31a5370261d8ce9dbf051c3ed53b51172dd34da696", size = 1935300, upload-time = "2025-10-07T10:50:27.715Z" }, - { url = "https://files.pythonhosted.org/packages/2b/00/9fe640194a1717a464ab861d43595c268830f98cb1e2705aa134b3544b70/pydantic_core-2.41.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc17b6ecf4983d298686014c92ebc955a9f9baf9f57dad4065e7906e7bee6222", size = 1970417, upload-time = "2025-10-07T10:50:29.573Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ad/f4cdfaf483b78ee65362363e73b6b40c48e067078d7b146e8816d5945ad6/pydantic_core-2.41.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:42ae9352cf211f08b04ea110563d6b1e415878eea5b4c70f6bdb17dca3b932d2", size = 2190745, upload-time = "2025-10-07T10:50:31.48Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c1/18f416d40a10f44e9387497ba449f40fdb1478c61ba05c4b6bdb82300362/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:e82947de92068b0a21681a13dd2102387197092fbe7defcfb8453e0913866506", size = 2150888, upload-time = "2025-10-07T10:50:33.477Z" }, - { url = "https://files.pythonhosted.org/packages/42/30/134c8a921630d8a88d6f905a562495a6421e959a23c19b0f49b660801d67/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e244c37d5471c9acdcd282890c6c4c83747b77238bfa19429b8473586c907656", size = 2324489, upload-time = "2025-10-07T10:50:36.48Z" }, - { url = "https://files.pythonhosted.org/packages/9c/48/a9263aeaebdec81e941198525b43edb3b44f27cfa4cb8005b8d3eb8dec72/pydantic_core-2.41.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1e798b4b304a995110d41ec93653e57975620ccb2842ba9420037985e7d7284e", size = 2322763, upload-time = "2025-10-07T10:50:38.751Z" }, - { url = "https://files.pythonhosted.org/packages/1d/62/755d2bd2593f701c5839fc084e9c2c5e2418f460383ad04e3b5d0befc3ca/pydantic_core-2.41.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f1fc716c0eb1663c59699b024428ad5ec2bcc6b928527b8fe28de6cb89f47efb", size = 2144046, upload-time = "2025-10-07T10:50:40.686Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, + { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, + { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, + { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, + { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, + { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, + { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, + { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, + { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, + { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, + { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, + { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, + { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, + { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, + { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, + { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, + { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, + { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, + { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, ] [[package]] name = "pydantic-settings" -version = "2.8.1" +version = "2.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, +] + +[[package]] +name = "pydocket" +version = "0.15.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "fakeredis", extra = ["lua"] }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-prometheus" }, + { name = "opentelemetry-instrumentation" }, + { name = "prometheus-client" }, + { name = "py-key-value-aio", extra = ["memory", "redis"] }, + { name = "python-json-logger" }, + { name = "redis" }, + { name = "rich" }, + { name = "typer" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/88/82/c79424d7d8c29b994fb01d277da57b0a9b09cc03c3ff875f9bd8a86b2145/pydantic_settings-2.8.1.tar.gz", hash = "sha256:d5c663dfbe9db9d5e1c646b2e161da12f0d734d422ee56f567d0ea2cee4e8585", size = 83550, upload-time = "2025-02-27T10:10:32.338Z" } +sdist = { url = "https://files.pythonhosted.org/packages/86/77/842e41be3cf3592b971cf42b24cae76e282294f474dc2dbf7cd6808d1b09/pydocket-0.15.5.tar.gz", hash = "sha256:b3af47702a293dd1da2e5e0f8f73f27fd3b3c95e36de72a2f71026d16908d5ba", size = 277245, upload-time = "2025-12-12T22:28:47.32Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/53/a64f03044927dc47aafe029c42a5b7aabc38dfb813475e0e1bf71c4a59d0/pydantic_settings-2.8.1-py3-none-any.whl", hash = "sha256:81942d5ac3d905f7f3ee1a70df5dfb62d5569c12f51a5a647defc1c3d9ee2e9c", size = 30839, upload-time = "2025-02-27T10:10:30.711Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c0/fdbc6e04e3369b90c6bf6567bc62871cf59e88550b94529821500dc807c1/pydocket-0.15.5-py3-none-any.whl", hash = "sha256:ad0d86c9a1bea394e875bcf8c793be2d0a7ebd1891bfe99e2e9eaf99ef0cb42e", size = 58517, upload-time = "2025-12-12T22:28:45.598Z" }, ] [[package]] name = "pygments" -version = "2.19.1" +version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581, upload-time = "2025-01-06T17:26:30.443Z" } +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/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293, upload-time = "2025-01-06T17:26:25.553Z" }, + { 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]] @@ -1025,43 +1174,49 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, { name = "iniconfig" }, { name = "packaging" }, { name = "pluggy" }, { name = "pygments" }, - { name = "tomli", marker = "python_full_version < '3.11'" }, ] -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" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.2.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "backports-asyncio-runner", marker = "python_full_version < '3.11'" }, { 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" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] name = "python-dotenv" -version = "1.1.1" +version = "1.2.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" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/26/19cadc79a718c5edbec86fd4919a6b6d3f681039a2f6d66d14be94e75fb9/python_dotenv-1.2.1.tar.gz", hash = "sha256:42667e897e16ab0d66954af0e60a9caa94f0fd4ecf3aaf6d2d260eec1aa36ad6", size = 44221, upload-time = "2025-10-26T15:12:10.434Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" }, +] + +[[package]] +name = "python-json-logger" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" }, ] [[package]] @@ -1078,9 +1233,6 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/40/44efbb0dfbd33aca6a6483191dae0716070ed99e2ecb0c53683f400a0b4f/pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3", size = 8760432, upload-time = "2025-07-14T20:13:05.9Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bf/360243b1e953bd254a82f12653974be395ba880e7ec23e3731d9f73921cc/pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b", size = 9590103, upload-time = "2025-07-14T20:13:07.698Z" }, - { url = "https://files.pythonhosted.org/packages/57/38/d290720e6f138086fb3d5ffe0b6caa019a791dd57866940c82e4eeaf2012/pywin32-311-cp310-cp310-win_arm64.whl", hash = "sha256:0502d1facf1fed4839a9a51ccbcc63d952cf318f78ffc00a7e78528ac27d7a2b", size = 8778557, upload-time = "2025-07-14T20:13:11.11Z" }, { 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" }, @@ -1110,15 +1262,6 @@ 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/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, { 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" }, @@ -1168,6 +1311,18 @@ wheels = [ { 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 = "redis" +version = "7.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "async-timeout", marker = "python_full_version < '3.11.3'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" }, +] + [[package]] name = "referencing" version = "0.36.2" @@ -1199,16 +1354,15 @@ wheels = [ [[package]] name = "rich" -version = "13.9.4" +version = "14.2.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149, upload-time = "2024-11-01T16:43:57.873Z" } +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/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424, upload-time = "2024-11-01T16:43:55.817Z" }, + { 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]] @@ -1226,137 +1380,110 @@ wheels = [ [[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/a5/ed/3aef893e2dd30e77e35d20d4ddb45ca459db59cead748cad9796ad479411/rpds_py-0.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:68afeec26d42ab3b47e541b272166a0b4400313946871cba3ed3a4fc0cab1cef", size = 371606, upload-time = "2025-08-27T12:12:25.189Z" }, - { url = "https://files.pythonhosted.org/packages/6d/82/9818b443e5d3eb4c83c3994561387f116aae9833b35c484474769c4a8faf/rpds_py-0.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74e5b2f7bb6fa38b1b10546d27acbacf2a022a8b5543efb06cfebc72a59c85be", size = 353452, upload-time = "2025-08-27T12:12:27.433Z" }, - { url = "https://files.pythonhosted.org/packages/99/c7/d2a110ffaaa397fc6793a83c7bd3545d9ab22658b7cdff05a24a4535cc45/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9024de74731df54546fab0bfbcdb49fae19159ecaecfc8f37c18d2c7e2c0bd61", size = 381519, upload-time = "2025-08-27T12:12:28.719Z" }, - { url = "https://files.pythonhosted.org/packages/5a/bc/e89581d1f9d1be7d0247eaef602566869fdc0d084008ba139e27e775366c/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:31d3ebadefcd73b73928ed0b2fd696f7fefda8629229f81929ac9c1854d0cffb", size = 394424, upload-time = "2025-08-27T12:12:30.207Z" }, - { url = "https://files.pythonhosted.org/packages/ac/2e/36a6861f797530e74bb6ed53495f8741f1ef95939eed01d761e73d559067/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2e7f8f169d775dd9092a1743768d771f1d1300453ddfe6325ae3ab5332b4657", size = 523467, upload-time = "2025-08-27T12:12:31.808Z" }, - { url = "https://files.pythonhosted.org/packages/c4/59/c1bc2be32564fa499f988f0a5c6505c2f4746ef96e58e4d7de5cf923d77e/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d905d16f77eb6ab2e324e09bfa277b4c8e5e6b8a78a3e7ff8f3cdf773b4c013", size = 402660, upload-time = "2025-08-27T12:12:33.444Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ec/ef8bf895f0628dd0a59e54d81caed6891663cb9c54a0f4bb7da918cb88cf/rpds_py-0.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50c946f048209e6362e22576baea09193809f87687a95a8db24e5fbdb307b93a", size = 384062, upload-time = "2025-08-27T12:12:34.857Z" }, - { url = "https://files.pythonhosted.org/packages/69/f7/f47ff154be8d9a5e691c083a920bba89cef88d5247c241c10b9898f595a1/rpds_py-0.27.1-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:3deab27804d65cd8289eb814c2c0e807c4b9d9916c9225e363cb0cf875eb67c1", size = 401289, upload-time = "2025-08-27T12:12:36.085Z" }, - { url = "https://files.pythonhosted.org/packages/3b/d9/ca410363efd0615814ae579f6829cafb39225cd63e5ea5ed1404cb345293/rpds_py-0.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8b61097f7488de4be8244c89915da8ed212832ccf1e7c7753a25a394bf9b1f10", size = 417718, upload-time = "2025-08-27T12:12:37.401Z" }, - { url = "https://files.pythonhosted.org/packages/e3/a0/8cb5c2ff38340f221cc067cc093d1270e10658ba4e8d263df923daa18e86/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8a3f29aba6e2d7d90528d3c792555a93497fe6538aa65eb675b44505be747808", size = 558333, upload-time = "2025-08-27T12:12:38.672Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8c/1b0de79177c5d5103843774ce12b84caa7164dfc6cd66378768d37db11bf/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:dd6cd0485b7d347304067153a6dc1d73f7d4fd995a396ef32a24d24b8ac63ac8", size = 589127, upload-time = "2025-08-27T12:12:41.48Z" }, - { url = "https://files.pythonhosted.org/packages/c8/5e/26abb098d5e01266b0f3a2488d299d19ccc26849735d9d2b95c39397e945/rpds_py-0.27.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6f4461bf931108c9fa226ffb0e257c1b18dc2d44cd72b125bec50ee0ab1248a9", size = 554899, upload-time = "2025-08-27T12:12:42.925Z" }, - { url = "https://files.pythonhosted.org/packages/de/41/905cc90ced13550db017f8f20c6d8e8470066c5738ba480d7ba63e3d136b/rpds_py-0.27.1-cp310-cp310-win32.whl", hash = "sha256:ee5422d7fb21f6a00c1901bf6559c49fee13a5159d0288320737bbf6585bd3e4", size = 217450, upload-time = "2025-08-27T12:12:44.813Z" }, - { url = "https://files.pythonhosted.org/packages/75/3d/6bef47b0e253616ccdf67c283e25f2d16e18ccddd38f92af81d5a3420206/rpds_py-0.27.1-cp310-cp310-win_amd64.whl", hash = "sha256:3e039aabf6d5f83c745d5f9a0a381d031e9ed871967c0a5c38d201aca41f3ba1", size = 228447, upload-time = "2025-08-27T12:12:46.204Z" }, - { 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/d5/63/b7cc415c345625d5e62f694ea356c58fb964861409008118f1245f8c3347/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:7ba22cb9693df986033b91ae1d7a979bc399237d45fccf875b76f62bb9e52ddf", size = 371360, upload-time = "2025-08-27T12:15:29.218Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/12e1b24b560cf378b8ffbdb9dc73abd529e1adcfcf82727dfd29c4a7b88d/rpds_py-0.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5b640501be9288c77738b5492b3fd3abc4ba95c50c2e41273c8a1459f08298d3", size = 353933, upload-time = "2025-08-27T12:15:30.837Z" }, - { url = "https://files.pythonhosted.org/packages/9b/85/1bb2210c1f7a1b99e91fea486b9f0f894aa5da3a5ec7097cbad7dec6d40f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb08b65b93e0c6dd70aac7f7890a9c0938d5ec71d5cb32d45cf844fb8ae47636", size = 382962, upload-time = "2025-08-27T12:15:32.348Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c9/a839b9f219cf80ed65f27a7f5ddbb2809c1b85c966020ae2dff490e0b18e/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d7ff07d696a7a38152ebdb8212ca9e5baab56656749f3d6004b34ab726b550b8", size = 394412, upload-time = "2025-08-27T12:15:33.839Z" }, - { url = "https://files.pythonhosted.org/packages/02/2d/b1d7f928b0b1f4fc2e0133e8051d199b01d7384875adc63b6ddadf3de7e5/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fb7c72262deae25366e3b6c0c0ba46007967aea15d1eea746e44ddba8ec58dcc", size = 523972, upload-time = "2025-08-27T12:15:35.377Z" }, - { url = "https://files.pythonhosted.org/packages/a9/af/2cbf56edd2d07716df1aec8a726b3159deb47cb5c27e1e42b71d705a7c2f/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7b002cab05d6339716b03a4a3a2ce26737f6231d7b523f339fa061d53368c9d8", size = 403273, upload-time = "2025-08-27T12:15:37.051Z" }, - { url = "https://files.pythonhosted.org/packages/c0/93/425e32200158d44ff01da5d9612c3b6711fe69f606f06e3895511f17473b/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23f6b69d1c26c4704fec01311963a41d7de3ee0570a84ebde4d544e5a1859ffc", size = 385278, upload-time = "2025-08-27T12:15:38.571Z" }, - { url = "https://files.pythonhosted.org/packages/eb/1a/1a04a915ecd0551bfa9e77b7672d1937b4b72a0fc204a17deef76001cfb2/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:530064db9146b247351f2a0250b8f00b289accea4596a033e94be2389977de71", size = 402084, upload-time = "2025-08-27T12:15:40.529Z" }, - { url = "https://files.pythonhosted.org/packages/51/f7/66585c0fe5714368b62951d2513b684e5215beaceab2c6629549ddb15036/rpds_py-0.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b90b0496570bd6b0321724a330d8b545827c4df2034b6ddfc5f5275f55da2ad", size = 419041, upload-time = "2025-08-27T12:15:42.191Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7e/83a508f6b8e219bba2d4af077c35ba0e0cdd35a751a3be6a7cba5a55ad71/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:879b0e14a2da6a1102a3fc8af580fc1ead37e6d6692a781bd8c83da37429b5ab", size = 560084, upload-time = "2025-08-27T12:15:43.839Z" }, - { url = "https://files.pythonhosted.org/packages/66/66/bb945683b958a1b19eb0fe715594630d0f36396ebdef4d9b89c2fa09aa56/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:0d807710df3b5faa66c731afa162ea29717ab3be17bdc15f90f2d9f183da4059", size = 590115, upload-time = "2025-08-27T12:15:46.647Z" }, - { url = "https://files.pythonhosted.org/packages/12/00/ccfaafaf7db7e7adace915e5c2f2c2410e16402561801e9c7f96683002d3/rpds_py-0.27.1-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:3adc388fc3afb6540aec081fa59e6e0d3908722771aa1e37ffe22b220a436f0b", size = 556561, upload-time = "2025-08-27T12:15:48.219Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b7/92b6ed9aad103bfe1c45df98453dfae40969eef2cb6c6239c58d7e96f1b3/rpds_py-0.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c796c0c1cc68cb08b0284db4229f5af76168172670c74908fdbd4b7d7f515819", size = 229125, upload-time = "2025-08-27T12:15:49.956Z" }, - { 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" }, +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/6e/f964e88b3d2abee2a82c1ac8366da848fce1c6d834dc2132c3fda3970290/rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425", size = 370157, upload-time = "2025-11-30T20:21:53.789Z" }, + { url = "https://files.pythonhosted.org/packages/94/ba/24e5ebb7c1c82e74c4e4f33b2112a5573ddc703915b13a073737b59b86e0/rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d", size = 359676, upload-time = "2025-11-30T20:21:55.475Z" }, + { url = "https://files.pythonhosted.org/packages/84/86/04dbba1b087227747d64d80c3b74df946b986c57af0a9f0c98726d4d7a3b/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4", size = 389938, upload-time = "2025-11-30T20:21:57.079Z" }, + { url = "https://files.pythonhosted.org/packages/42/bb/1463f0b1722b7f45431bdd468301991d1328b16cffe0b1c2918eba2c4eee/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f", size = 402932, upload-time = "2025-11-30T20:21:58.47Z" }, + { url = "https://files.pythonhosted.org/packages/99/ee/2520700a5c1f2d76631f948b0736cdf9b0acb25abd0ca8e889b5c62ac2e3/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4", size = 525830, upload-time = "2025-11-30T20:21:59.699Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ad/bd0331f740f5705cc555a5e17fdf334671262160270962e69a2bdef3bf76/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97", size = 412033, upload-time = "2025-11-30T20:22:00.991Z" }, + { url = "https://files.pythonhosted.org/packages/f8/1e/372195d326549bb51f0ba0f2ecb9874579906b97e08880e7a65c3bef1a99/rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89", size = 390828, upload-time = "2025-11-30T20:22:02.723Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2b/d88bb33294e3e0c76bc8f351a3721212713629ffca1700fa94979cb3eae8/rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d", size = 404683, upload-time = "2025-11-30T20:22:04.367Z" }, + { url = "https://files.pythonhosted.org/packages/50/32/c759a8d42bcb5289c1fac697cd92f6fe01a018dd937e62ae77e0e7f15702/rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038", size = 421583, upload-time = "2025-11-30T20:22:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/2b/81/e729761dbd55ddf5d84ec4ff1f47857f4374b0f19bdabfcf929164da3e24/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7", size = 572496, upload-time = "2025-11-30T20:22:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/14/f6/69066a924c3557c9c30baa6ec3a0aa07526305684c6f86c696b08860726c/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed", size = 598669, upload-time = "2025-11-30T20:22:09.312Z" }, + { url = "https://files.pythonhosted.org/packages/5f/48/905896b1eb8a05630d20333d1d8ffd162394127b74ce0b0784ae04498d32/rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85", size = 561011, upload-time = "2025-11-30T20:22:11.309Z" }, + { url = "https://files.pythonhosted.org/packages/22/16/cd3027c7e279d22e5eb431dd3c0fbc677bed58797fe7581e148f3f68818b/rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c", size = 221406, upload-time = "2025-11-30T20:22:13.101Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/e7b7aa136f28462b344e652ee010d4de26ee9fd16f1bfd5811f5153ccf89/rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825", size = 236024, upload-time = "2025-11-30T20:22:14.853Z" }, + { url = "https://files.pythonhosted.org/packages/14/a6/364bba985e4c13658edb156640608f2c9e1d3ea3c81b27aa9d889fff0e31/rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229", size = 229069, upload-time = "2025-11-30T20:22:16.577Z" }, + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, + { url = "https://files.pythonhosted.org/packages/69/71/3f34339ee70521864411f8b6992e7ab13ac30d8e4e3309e07c7361767d91/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58", size = 372292, upload-time = "2025-11-30T20:24:16.537Z" }, + { url = "https://files.pythonhosted.org/packages/57/09/f183df9b8f2d66720d2ef71075c59f7e1b336bec7ee4c48f0a2b06857653/rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a", size = 362128, upload-time = "2025-11-30T20:24:18.086Z" }, + { url = "https://files.pythonhosted.org/packages/7a/68/5c2594e937253457342e078f0cc1ded3dd7b2ad59afdbf2d354869110a02/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb", size = 391542, upload-time = "2025-11-30T20:24:20.092Z" }, + { url = "https://files.pythonhosted.org/packages/49/5c/31ef1afd70b4b4fbdb2800249f34c57c64beb687495b10aec0365f53dfc4/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c", size = 404004, upload-time = "2025-11-30T20:24:22.231Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/0cfbea38d05756f3440ce6534d51a491d26176ac045e2707adc99bb6e60a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3", size = 527063, upload-time = "2025-11-30T20:24:24.302Z" }, + { url = "https://files.pythonhosted.org/packages/42/e6/01e1f72a2456678b0f618fc9a1a13f882061690893c192fcad9f2926553a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5", size = 413099, upload-time = "2025-11-30T20:24:25.916Z" }, + { url = "https://files.pythonhosted.org/packages/b8/25/8df56677f209003dcbb180765520c544525e3ef21ea72279c98b9aa7c7fb/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738", size = 392177, upload-time = "2025-11-30T20:24:27.834Z" }, + { url = "https://files.pythonhosted.org/packages/4a/b4/0a771378c5f16f8115f796d1f437950158679bcd2a7c68cf251cfb00ed5b/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f", size = 406015, upload-time = "2025-11-30T20:24:29.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/d8/456dbba0af75049dc6f63ff295a2f92766b9d521fa00de67a2bd6427d57a/rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877", size = 423736, upload-time = "2025-11-30T20:24:31.22Z" }, + { url = "https://files.pythonhosted.org/packages/13/64/b4d76f227d5c45a7e0b796c674fd81b0a6c4fbd48dc29271857d8219571c/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a", size = 573981, upload-time = "2025-11-30T20:24:32.934Z" }, + { url = "https://files.pythonhosted.org/packages/20/91/092bacadeda3edf92bf743cc96a7be133e13a39cdbfd7b5082e7ab638406/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4", size = 599782, upload-time = "2025-11-30T20:24:35.169Z" }, + { url = "https://files.pythonhosted.org/packages/d1/b7/b95708304cd49b7b6f82fdd039f1748b66ec2b21d6a45180910802f1abf1/rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e", size = 562191, upload-time = "2025-11-30T20:24:36.853Z" }, ] [[package]] @@ -1373,86 +1500,61 @@ wheels = [ ] [[package]] -name = "sniffio" -version = "1.3.1" +name = "shellingham" +version = "1.5.4" 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" } +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/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, + { 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 = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594, upload-time = "2021-05-16T22:03:42.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575, upload-time = "2021-05-16T22:03:41.177Z" }, ] [[package]] name = "sse-starlette" -version = "2.2.1" +version = "3.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "starlette" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/a4/80d2a11af59fe75b48230846989e93979c892d3a20016b42bb44edb9e398/sse_starlette-2.2.1.tar.gz", hash = "sha256:54470d5f19274aeed6b2d473430b08b4b379ea851d953b11d7f1c4a2c118b419", size = 17376, upload-time = "2024-12-25T09:09:30.616Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/3c/fa6517610dc641262b77cc7bf994ecd17465812c1b0585fe33e11be758ab/sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971", size = 21943, upload-time = "2025-10-30T18:44:20.117Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/e0/5b8bd393f27f4a62461c5cf2479c75a2cc2ffa330976f9f00f5f6e4f50eb/sse_starlette-2.2.1-py3-none-any.whl", hash = "sha256:6410a3d3ba0c89e7675d4c273a301d64649c03a5ef1ca101f10b47f895fd0e99", size = 10120, upload-time = "2024-12-25T09:09:26.761Z" }, + { url = "https://files.pythonhosted.org/packages/23/a0/984525d19ca5c8a6c33911a0c164b11490dd0f90ff7fd689f704f84e9a11/sse_starlette-3.0.3-py3-none-any.whl", hash = "sha256:af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431", size = 11765, upload-time = "2025-10-30T18:44:18.834Z" }, ] [[package]] name = "starlette" -version = "0.46.1" +version = "0.50.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/04/1b/52b27f2e13ceedc79a908e29eac426a63465a1a01248e5f24aa36a62aeb3/starlette-0.46.1.tar.gz", hash = "sha256:3c88d58ee4bd1bb807c0d1acb381838afc7752f9ddaec81bbe4383611d833230", size = 2580102, upload-time = "2025-03-08T10:55:34.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/4b/528ccf7a982216885a1ff4908e886b8fb5f19862d1962f56a3fce2435a70/starlette-0.46.1-py3-none-any.whl", hash = "sha256:77c74ed9d2720138b25875133f3a2dae6d854af2ec37dceb56aef370c1d8a227", size = 71995, upload-time = "2025-03-08T10:55:32.662Z" }, + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] [[package]] -name = "tomli" -version = "2.3.0" +name = "typer" +version = "0.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +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]] @@ -1478,11 +1580,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" 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" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } 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" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] [[package]] @@ -1492,7 +1594,6 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "h11" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] 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 = [ @@ -1505,17 +1606,6 @@ 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/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" }, - { url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" }, - { url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" }, - { url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" }, - { url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" }, - { url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" }, - { url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" }, - { url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" }, - { url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" }, - { url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" }, { 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" }, @@ -1549,15 +1639,68 @@ wheels = [ { 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/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" }, - { url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" }, - { url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" }, - { url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" }, - { url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" }, { 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 = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, +] + [[package]] name = "zipp" version = "3.23.0" diff --git a/MCPForUnity/Server~/wrapper.js b/MCPForUnity/Server~/wrapper.js new file mode 100644 index 000000000..2350bc6f3 --- /dev/null +++ b/MCPForUnity/Server~/wrapper.js @@ -0,0 +1,112 @@ +const { spawn } = require("child_process"); +const path = require("path"); +const fs = require("fs"); + +const LOG_FILE = path.join(__dirname, "wrapper.log"); + +// Simple logging helper +function log(msg) { + try { + fs.appendFileSync(LOG_FILE, new Date().toISOString() + ": " + msg + "\n"); + } catch (e) { + // Ignore logging errors + } +} + +log("Wrapper started"); + +const serverDir = __dirname; + +// Use uv run with --quiet to minimize noise +// Add --quiet to uv run commands to suppress "resolved ..." messages +const pythonProcess = spawn( + "uv", + ["run", "--quiet", "src/main.py", "--transport", "stdio"], + { + cwd: serverDir, + stdio: ["pipe", "pipe", "pipe"], + shell: true, // Needed for windows command resolution sometimes + env: { + ...process.env, + PYTHONUNBUFFERED: "1", + PYTHONIOENCODING: "utf-8", + HOME: process.env.USERPROFILE, // Ensure uv finds home on Windows + }, + } +); + +let buffer = ""; + +// Handle stdout: Filter non-JSON lines +pythonProcess.stdout.on("data", (data) => { + buffer += data.toString("utf8"); + + let newlineIdx; + while ((newlineIdx = buffer.indexOf("\n")) !== -1) { + // Extract line + let line = buffer.substring(0, newlineIdx).trim(); + // Move buffer forward + buffer = buffer.substring(newlineIdx + 1); + + if (!line) continue; + + try { + // Validate JSON. If strictly JSON-RPC, it must be an object. + // We don't parse fully to save perf, but JSON.parse ensures validity. + JSON.parse(line); + + // If valid, pass to stdout with a clean newline + process.stdout.write(line + "\n"); + } catch (e) { + // If not JSON, it is likely a log message or noise. + // Redirect to stderr so the client (Antigravity/Cursor) doesn't crash. + log("FILTERED STDOUT: " + line); + process.stderr.write("[STDOUT_LOG] " + line + "\n"); + } + } +}); + +// Handle stderr: Pass through but log +pythonProcess.stderr.on("data", (data) => { + const msg = data.toString("utf8"); + log("STDERR: " + msg); // Enabled for debugging + process.stderr.write(data); +}); + +pythonProcess.on("error", (err) => { + log("Failed to spawn process: " + err.message); + process.exit(1); +}); + +pythonProcess.on("exit", (code) => { + log("Python process exited with code " + code); + process.exit(code || 0); +}); + +// Forward stdin to python process +process.stdin.pipe(pythonProcess.stdin); + +// Cleanup on exit +function cleanup() { + if (pythonProcess) { + try { + pythonProcess.kill(); + if (process.platform === "win32") { + require("child_process").execSync( + `taskkill /pid ${pythonProcess.pid} /T /F` + ); + } + } catch (e) { + /* ignore */ + } + } +} + +process.on("SIGINT", () => { + cleanup(); + process.exit(); +}); +process.on("SIGTERM", () => { + cleanup(); + process.exit(); +}); diff --git a/Server/pyproject.toml b/Server/pyproject.toml deleted file mode 100644 index c8138ba35..000000000 --- a/Server/pyproject.toml +++ /dev/null @@ -1,28 +0,0 @@ -[project] -name = "MCPForUnityServer" -version = "8.2.3" -description = "MCP for Unity Server: A Unity package for Unity Editor integration via the Model Context Protocol (MCP)." -readme = "README.md" -requires-python = ">=3.10" -dependencies = [ - "httpx>=0.27.2", - "fastmcp>=2.13.0,<2.13.2", - "mcp>=1.16.0", - "pydantic>=2.12.0", - "tomli>=2.3.0", - "fastapi>=0.104.0", - "uvicorn>=0.35.0", -] - -[project.optional-dependencies] -dev = [ - "pytest>=8.0.0", - "pytest-asyncio>=0.23", -] - -[project.scripts] -mcp-for-unity = "main:main" - -[build-system] -requires = ["setuptools>=64.0.0", "wheel"] -build-backend = "setuptools.build_meta" From 8a76ca1371f452d114846b409d40630ae55304b5 Mon Sep 17 00:00:00 2001 From: choej2303 Date: Sun, 14 Dec 2025 10:32:45 +0900 Subject: [PATCH 2/6] =?UTF-8?q?Clean=20up=20root=20directory=20and=20unuse?= =?UTF-8?q?d=20scripts=20/=20=EB=A3=A8=ED=8A=B8=20=EB=94=94=EB=A0=89?= =?UTF-8?q?=ED=86=A0=EB=A6=AC=20=EB=B0=8F=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- claude_skill_unity.zip | Bin 75297 -> 0 bytes deploy-dev.bat | 151 ----------------------------- mcp_source.py | 173 --------------------------------- prune_tool_results.py | 58 ------------ restore-dev.bat | 179 ----------------------------------- test_unity_socket_framing.py | 105 -------------------- 6 files changed, 666 deletions(-) delete mode 100644 claude_skill_unity.zip delete mode 100644 deploy-dev.bat delete mode 100755 mcp_source.py delete mode 100755 prune_tool_results.py delete mode 100644 restore-dev.bat delete mode 100644 test_unity_socket_framing.py diff --git a/claude_skill_unity.zip b/claude_skill_unity.zip deleted file mode 100644 index 3584418b80aacf5b0181f7b547f38ad80f343e09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 75297 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 G5bO8UVuFoRqv=V3!|54dcp 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 O8v_7%NeO@A1O5Z49$L}> diff --git a/deploy-dev.bat b/deploy-dev.bat deleted file mode 100644 index a99115bba..000000000 --- a/deploy-dev.bat +++ /dev/null @@ -1,151 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -echo =============================================== -echo MCP for Unity Development Deployment Script -echo =============================================== -echo. - -:: Configuration -set "SCRIPT_DIR=%~dp0" -set "BRIDGE_SOURCE=%SCRIPT_DIR%MCPForUnity" -set "SERVER_SOURCE=%SCRIPT_DIR%Server" -set "DEFAULT_BACKUP_DIR=%USERPROFILE%\Desktop\unity-mcp-backup" -set "DEFAULT_SERVER_PATH=%LOCALAPPDATA%\UnityMCP\UnityMcpServer\src" - -:: Get user inputs -echo Please provide the following paths: -echo. - -:: Package cache location -echo Unity Package Cache Location: -echo Example: X:\Unity\Projects\UnityMCPTestbed2\Library\PackageCache\com.coplaydev.unity-mcp@4c106125b342 -set /p "PACKAGE_CACHE_PATH=Enter Unity package cache path: " - -if "%PACKAGE_CACHE_PATH%"=="" ( - echo Error: Package cache path cannot be empty! - pause - exit /b 1 -) - -rem Server installation path prompt disabled (server deploy skipped) -set "SERVER_PATH=" - -:: Backup location (with default) -echo. -echo Backup Location: -echo Default: %DEFAULT_BACKUP_DIR% -set /p "BACKUP_DIR=Enter backup directory (or press Enter for default): " -if "%BACKUP_DIR%"=="" set "BACKUP_DIR=%DEFAULT_BACKUP_DIR%" - -:: Validation -echo. -echo =============================================== -echo Validating paths... -echo =============================================== - -if not exist "%BRIDGE_SOURCE%" ( - echo Error: Bridge source not found: %BRIDGE_SOURCE% - pause - exit /b 1 -) - -if not exist "%PACKAGE_CACHE_PATH%" ( - echo Error: Package cache path not found: %PACKAGE_CACHE_PATH% - pause - exit /b 1 -) - -:: Create backup directory -if not exist "%BACKUP_DIR%" ( - echo Creating backup directory: %BACKUP_DIR% - mkdir "%BACKUP_DIR%" -) - -:: Create timestamped backup subdirectory -set "TIMESTAMP=%date:~-4,4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2%%time:~6,2%" -set "TIMESTAMP=%TIMESTAMP: =0%" -set "TIMESTAMP=%TIMESTAMP::=-%" -set "TIMESTAMP=%TIMESTAMP:/=-%" -set "BACKUP_SUBDIR=%BACKUP_DIR%\backup_%TIMESTAMP%" -mkdir "%BACKUP_SUBDIR%" - -echo. -echo =============================================== -echo Starting deployment... -echo =============================================== - -:: Backup original files -echo Creating backup of original files... -if exist "%PACKAGE_CACHE_PATH%\Editor" ( - echo Backing up Unity Bridge files... - xcopy "%PACKAGE_CACHE_PATH%\Editor" "%BACKUP_SUBDIR%\UnityBridge\Editor\" /E /I /Y > nul - if !errorlevel! neq 0 ( - echo Error: Failed to backup Unity Bridge files - pause - exit /b 1 - ) -) - -if exist "%PACKAGE_CACHE_PATH%\Runtime" ( - echo Backing up Unity Runtime files... - xcopy "%PACKAGE_CACHE_PATH%\Runtime" "%BACKUP_SUBDIR%\UnityBridge\Runtime\" /E /I /Y > nul - if !errorlevel! neq 0 ( - echo Error: Failed to backup Unity Runtime files - pause - exit /b 1 - ) -) - -rem Server backup skipped (deprecated legacy deploy) -rem if exist "%SERVER_PATH%" ( -rem echo Backing up Python Server files... -rem xcopy "%SERVER_PATH%\*" "%BACKUP_SUBDIR%\PythonServer\" /E /I /Y > nul -rem if !errorlevel! neq 0 ( -rem echo Error: Failed to backup Python Server files -rem pause -rem exit /b 1 -rem ) -rem ) - -:: Deploy Unity Bridge -echo. -echo Deploying Unity Bridge code... -xcopy "%BRIDGE_SOURCE%\Editor\*" "%PACKAGE_CACHE_PATH%\Editor\" /E /Y > nul -if !errorlevel! neq 0 ( - echo Error: Failed to deploy Unity Bridge code - pause - exit /b 1 -) - -echo Deploying Unity Runtime code... -xcopy "%BRIDGE_SOURCE%\Runtime\*" "%PACKAGE_CACHE_PATH%\Runtime\" /E /Y > nul -if !errorlevel! neq 0 ( - echo Error: Failed to deploy Unity Runtime code - pause - exit /b 1 -) - -rem Deploy Python Server (disabled; server no longer deployed this way) -rem echo Deploying Python Server code... -rem xcopy "%SERVER_SOURCE%\*" "%SERVER_PATH%\" /E /Y > nul -rem if !errorlevel! neq 0 ( -rem echo Error: Failed to deploy Python Server code -rem pause -rem exit /b 1 -rem ) - -:: Success -echo. -echo =============================================== -echo Deployment completed successfully! -echo =============================================== -echo. -echo Backup created at: %BACKUP_SUBDIR% -echo. -echo Next steps: -echo 1. Restart Unity Editor to load new Bridge code -echo 2. Restart any MCP clients to use new Server code -echo 3. Use restore-dev.bat to rollback if needed -echo. -pause \ No newline at end of file diff --git a/mcp_source.py b/mcp_source.py deleted file mode 100755 index eda374e91..000000000 --- a/mcp_source.py +++ /dev/null @@ -1,173 +0,0 @@ -#!/usr/bin/env python3 -""" -Generic helper to switch the MCP for Unity package source in a Unity project's -Packages/manifest.json. This is useful for switching between upstream and local repos while working on the MCP. - -Usage: - python mcp_source.py [--manifest /abs/path/to/manifest.json] [--repo /abs/path/to/unity-mcp] [--choice 1|2|3] - -Choices: - 1) Upstream main (CoplayDev/unity-mcp) - 2) Your remote current branch (derived from `origin` and current branch) - 3) Local repo workspace (file: URL to MCPForUnity in your checkout) -""" - -from __future__ import annotations - -import argparse -import json -import pathlib -import subprocess -import sys - -PKG_NAME = "com.coplaydev.unity-mcp" -BRIDGE_SUBPATH = "MCPForUnity" - - -def run_git(repo: pathlib.Path, *args: str) -> str: - result = subprocess.run([ - "git", "-C", str(repo), *args - ], capture_output=True, text=True) - if result.returncode != 0: - raise RuntimeError(result.stderr.strip() - or f"git {' '.join(args)} failed") - return result.stdout.strip() - - -def normalize_origin_to_https(url: str) -> str: - """Map common SSH origin forms to https for Unity's git URL scheme.""" - if url.startswith("git@github.com:"): - owner_repo = url.split(":", 1)[1] - if owner_repo.endswith(".git"): - owner_repo = owner_repo[:-4] - return f"https://github.com/{owner_repo}.git" - # already https or file: etc. - return url - - -def detect_repo_root(explicit: str | None) -> pathlib.Path: - if explicit: - return pathlib.Path(explicit).resolve() - # Prefer the git toplevel from the script's directory - here = pathlib.Path(__file__).resolve().parent - try: - top = run_git(here, "rev-parse", "--show-toplevel") - return pathlib.Path(top) - except Exception: - return here - - -def detect_branch(repo: pathlib.Path) -> str: - return run_git(repo, "rev-parse", "--abbrev-ref", "HEAD") - - -def detect_origin(repo: pathlib.Path) -> str: - url = run_git(repo, "remote", "get-url", "origin") - return normalize_origin_to_https(url) - - -def find_manifest(explicit: str | None) -> pathlib.Path: - if explicit: - return pathlib.Path(explicit).resolve() - # Walk up from CWD looking for Packages/manifest.json - cur = pathlib.Path.cwd().resolve() - for parent in [cur, *cur.parents]: - candidate = parent / "Packages" / "manifest.json" - if candidate.exists(): - return candidate - raise FileNotFoundError( - "Could not find Packages/manifest.json from current directory. Use --manifest to specify a path.") - - -def read_json(path: pathlib.Path) -> dict: - with path.open("r", encoding="utf-8") as f: - return json.load(f) - - -def write_json(path: pathlib.Path, data: dict) -> None: - with path.open("w", encoding="utf-8") as f: - json.dump(data, f, indent=2) - f.write("\n") - - -def build_options(repo_root: pathlib.Path, branch: str, origin_https: str): - upstream = "https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity" - # Ensure origin is https - origin = origin_https - # If origin is a local file path or non-https, try to coerce to https github if possible - if origin.startswith("file:"): - # Not meaningful for remote option; keep upstream - origin_remote = upstream - else: - origin_remote = origin - return [ - ("[1] Upstream main", upstream), - (f"[2] Remote {branch}", - f"{origin_remote}?path=/{BRIDGE_SUBPATH}#{branch}"), - (f"[3] Local {branch}", - f"file:{(repo_root / BRIDGE_SUBPATH).as_posix()}"), - ] - - -def parse_args() -> argparse.Namespace: - p = argparse.ArgumentParser( - description="Switch MCP for Unity package source") - p.add_argument("--manifest", help="Path to Packages/manifest.json") - p.add_argument( - "--repo", help="Path to unity-mcp repo root (for local file option)") - p.add_argument( - "--choice", choices=["1", "2", "3"], help="Pick option non-interactively") - return p.parse_args() - - -def main() -> None: - args = parse_args() - try: - repo_root = detect_repo_root(args.repo) - branch = detect_branch(repo_root) - origin = detect_origin(repo_root) - except Exception as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - options = build_options(repo_root, branch, origin) - - try: - manifest_path = find_manifest(args.manifest) - except Exception as e: - print(f"Error: {e}", file=sys.stderr) - sys.exit(1) - - print("Select MCP package source by number:") - for label, _ in options: - print(label) - - if args.choice: - choice = args.choice - else: - choice = input("Enter 1-3: ").strip() - - if choice not in {"1", "2", "3"}: - print("Invalid selection.", file=sys.stderr) - sys.exit(1) - - idx = int(choice) - 1 - _, chosen = options[idx] - - data = read_json(manifest_path) - deps = data.get("dependencies", {}) - if PKG_NAME not in deps: - print( - f"Error: '{PKG_NAME}' not found in manifest dependencies.", file=sys.stderr) - sys.exit(1) - - print(f"\nUpdating {PKG_NAME} → {chosen}") - deps[PKG_NAME] = chosen - data["dependencies"] = deps - write_json(manifest_path, data) - print(f"Done. Wrote to: {manifest_path}") - print("Tip: In Unity, open Package Manager and Refresh to re-resolve packages.") - - -if __name__ == "__main__": - main() diff --git a/prune_tool_results.py b/prune_tool_results.py deleted file mode 100755 index a3c5d7a4f..000000000 --- a/prune_tool_results.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -import sys, json - -def summarize(txt): - try: - obj = json.loads(txt) - except Exception: - return f"tool_result: {len(txt)} bytes" - data = obj.get("data", {}) or {} - msg = obj.get("message") or obj.get("status") or "" - # Common tool shapes - if "sha256" in str(data): - ln = data.get("lengthBytes") or data.get("length") or "" - return f"len={ln}".strip() - if "diagnostics" in data: - diags = data["diagnostics"] or [] - w = sum(d.get("severity","" ).lower()=="warning" for d in diags) - e = sum(d.get("severity","" ).lower() in ("error","fatal") for d in diags) - ok = "OK" if not e else "FAIL" - return f"validate: {ok} (warnings={w}, errors={e})" - if "matches" in data: - m = data["matches"] or [] - if m: - first = m[0] - return f"find_in_file: {len(m)} match(es) first@{first.get('line',0)}:{first.get('col',0)}" - return "find_in_file: 0 matches" - if "lines" in data: # console - lines = data["lines"] or [] - lvls = {"info":0,"warning":0,"error":0} - for L in lines: - lvls[L.get("level","" ).lower()] = lvls.get(L.get("level","" ).lower(),0)+1 - return f"console: {len(lines)} lines (info={lvls.get('info',0)},warn={lvls.get('warning',0)},err={lvls.get('error',0)})" - # Fallback: short status - return (msg or "tool_result")[:80] - -def prune_message(msg): - if "content" not in msg: return msg - newc=[] - for c in msg["content"]: - if c.get("type")=="tool_result" and c.get("content"): - out=[] - for chunk in c["content"]: - if chunk.get("type")=="text": - out.append({"type":"text","text":summarize(chunk.get("text","" ))}) - newc.append({"type":"tool_result","tool_use_id":c.get("tool_use_id"),"content":out}) - else: - newc.append(c) - msg["content"]=newc - return msg - -def main(): - convo=json.load(sys.stdin) - if isinstance(convo, dict) and "messages" in convo: - convo["messages"]=[prune_message(m) for m in convo["messages"]] - elif isinstance(convo, list): - convo=[prune_message(m) for m in convo] - json.dump(convo, sys.stdout, ensure_ascii=False) -main() diff --git a/restore-dev.bat b/restore-dev.bat deleted file mode 100644 index 6b6148140..000000000 --- a/restore-dev.bat +++ /dev/null @@ -1,179 +0,0 @@ -@echo off -setlocal enabledelayedexpansion - -echo =============================================== -echo MCP for Unity Development Restore Script -echo =============================================== -echo. -echo Note: The Python server is bundled under Server in the package. -echo This script restores your installed server path from backups, not the repo copy. -echo. - -:: Configuration -set "DEFAULT_BACKUP_DIR=%USERPROFILE%\Desktop\unity-mcp-backup" -set "DEFAULT_SERVER_PATH=%LOCALAPPDATA%\UnityMCP\UnityMcpServer\src" - -:: Get user inputs -echo Please provide the following paths: -echo. - -:: Package cache location -echo Unity Package Cache Location: -echo Example: X:\UnityProject\Library\PackageCache\com.coplaydev.unity-mcp@1.0.0 -set /p "PACKAGE_CACHE_PATH=Enter Unity package cache path: " - -if "%PACKAGE_CACHE_PATH%"=="" ( - echo Error: Package cache path cannot be empty! - pause - exit /b 1 -) - -:: Server installation path (with default) -echo. -echo Server Installation Path: -echo Default: %DEFAULT_SERVER_PATH% -set /p "SERVER_PATH=Enter server path (or press Enter for default): " -if "%SERVER_PATH%"=="" set "SERVER_PATH=%DEFAULT_SERVER_PATH%" - -:: Backup location (with default) -echo. -echo Backup Location: -echo Default: %DEFAULT_BACKUP_DIR% -set /p "BACKUP_DIR=Enter backup directory (or press Enter for default): " -if "%BACKUP_DIR%"=="" set "BACKUP_DIR=%DEFAULT_BACKUP_DIR%" - -:: List available backups -echo. -echo =============================================== -echo Available backups: -echo =============================================== -set "counter=0" -for /d %%d in ("%BACKUP_DIR%\backup_*") do ( - set /a counter+=1 - set "backup!counter!=%%d" - echo !counter!. %%~nxd -) - -if %counter%==0 ( - echo No backups found in %BACKUP_DIR% - pause - exit /b 1 -) - -echo. -set /p "choice=Select backup to restore (1-%counter%): " - -:: Validate choice -if "%choice%"=="" goto :invalid_choice -if %choice% lss 1 goto :invalid_choice -if %choice% gtr %counter% goto :invalid_choice - -set "SELECTED_BACKUP=!backup%choice%!" -echo. -echo Selected backup: %SELECTED_BACKUP% - -:: Validation -echo. -echo =============================================== -echo Validating paths... -echo =============================================== - -if not exist "%SELECTED_BACKUP%" ( - echo Error: Selected backup not found: %SELECTED_BACKUP% - pause - exit /b 1 -) - -if not exist "%PACKAGE_CACHE_PATH%" ( - echo Error: Package cache path not found: %PACKAGE_CACHE_PATH% - pause - exit /b 1 -) - -if not exist "%SERVER_PATH%" ( - echo Error: Server installation path not found: %SERVER_PATH% - pause - exit /b 1 -) - -:: Confirm restore -echo. -echo =============================================== -echo WARNING: This will overwrite current files! -echo =============================================== -echo Restoring from: %SELECTED_BACKUP% -echo Unity Bridge Editor target: %PACKAGE_CACHE_PATH%\Editor -echo Unity Bridge Runtime target: %PACKAGE_CACHE_PATH%\Runtime -echo Python Server target: %SERVER_PATH% -echo. -set /p "confirm=Continue with restore? (y/N): " -if /i not "%confirm%"=="y" ( - echo Restore cancelled. - pause - exit /b 0 -) - -echo. -echo =============================================== -echo Starting restore... -echo =============================================== - -:: Restore Unity Bridge -if exist "%SELECTED_BACKUP%\UnityBridge\Editor" ( - echo Restoring Unity Bridge Editor files... - rd /s /q "%PACKAGE_CACHE_PATH%\Editor" 2>nul - xcopy "%SELECTED_BACKUP%\UnityBridge\Editor\*" "%PACKAGE_CACHE_PATH%\Editor\" /E /I /Y > nul - if !errorlevel! neq 0 ( - echo Error: Failed to restore Unity Bridge Editor files - pause - exit /b 1 - ) -) else ( - echo Warning: No Unity Bridge Editor backup found, skipping... -) - -if exist "%SELECTED_BACKUP%\UnityBridge\Runtime" ( - echo Restoring Unity Bridge Runtime files... - rd /s /q "%PACKAGE_CACHE_PATH%\Runtime" 2>nul - xcopy "%SELECTED_BACKUP%\UnityBridge\Runtime\*" "%PACKAGE_CACHE_PATH%\Runtime\" /E /I /Y > nul - if !errorlevel! neq 0 ( - echo Error: Failed to restore Unity Bridge Runtime files - pause - exit /b 1 - ) -) else ( - echo Warning: No Unity Bridge Runtime backup found, skipping... -) - -:: Restore Python Server -if exist "%SELECTED_BACKUP%\PythonServer" ( - echo Restoring Python Server files... - rd /s /q "%SERVER_PATH%" 2>nul - mkdir "%SERVER_PATH%" - xcopy "%SELECTED_BACKUP%\PythonServer\*" "%SERVER_PATH%\" /E /I /Y > nul - if !errorlevel! neq 0 ( - echo Error: Failed to restore Python Server files - pause - exit /b 1 - ) -) else ( - echo Warning: No Python Server backup found, skipping... -) - -:: Success -echo. -echo =============================================== -echo Restore completed successfully! -echo =============================================== -echo. -echo Next steps: -echo 1. Restart Unity Editor to load restored Bridge code -echo 2. Restart any MCP clients to use restored Server code -echo. -pause -exit /b 0 - -:invalid_choice -echo Invalid choice. Please enter a number between 1 and %counter%. -pause -exit /b 1 \ No newline at end of file diff --git a/test_unity_socket_framing.py b/test_unity_socket_framing.py deleted file mode 100644 index 27d368557..000000000 --- a/test_unity_socket_framing.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -import socket -import struct -import json -import sys - -HOST = "127.0.0.1" -PORT = 6400 -try: - SIZE_MB = int(sys.argv[1]) -except (IndexError, ValueError): - SIZE_MB = 5 # e.g., 5 or 10 -FILL = "R" -MAX_FRAME = 64 * 1024 * 1024 - - -def recv_exact(sock, n): - buf = bytearray(n) - view = memoryview(buf) - off = 0 - while off < n: - r = sock.recv_into(view[off:]) - if r == 0: - raise RuntimeError("socket closed") - off += r - return bytes(buf) - - -def is_valid_json(b): - try: - json.loads(b.decode("utf-8")) - return True - except Exception: - return False - - -def recv_legacy_json(sock, timeout=60): - sock.settimeout(timeout) - chunks = [] - while True: - chunk = sock.recv(65536) - if not chunk: - data = b"".join(chunks) - if not data: - raise RuntimeError("no data, socket closed") - return data - chunks.append(chunk) - data = b"".join(chunks) - if data.strip() == b"ping": - return data - if is_valid_json(data): - return data - - -def main(): - # Cap filler to stay within framing limit (reserve small overhead for JSON) - safe_max = max(1, MAX_FRAME - 4096) - filler_len = min(SIZE_MB * 1024 * 1024, safe_max) - body = { - "type": "read_console", - "params": { - "action": "get", - "types": ["all"], - "count": 1000, - "format": "detailed", - "includeStacktrace": True, - "filterText": FILL * filler_len - } - } - body_bytes = json.dumps(body, ensure_ascii=False).encode("utf-8") - - with socket.create_connection((HOST, PORT), timeout=5) as s: - s.settimeout(2) - # Read optional greeting - try: - greeting = s.recv(256) - except Exception: - greeting = b"" - greeting_text = greeting.decode("ascii", errors="ignore").strip() - print(f"Greeting: {greeting_text or '(none)'}") - - framing = "FRAMING=1" in greeting_text - print(f"Using framing? {framing}") - - s.settimeout(120) - if framing: - header = struct.pack(">Q", len(body_bytes)) - s.sendall(header + body_bytes) - resp_len = struct.unpack(">Q", recv_exact(s, 8))[0] - print(f"Response framed length: {resp_len}") - MAX_RESP = MAX_FRAME - if resp_len <= 0 or resp_len > MAX_RESP: - raise RuntimeError( - f"invalid framed length: {resp_len} (max {MAX_RESP})") - resp = recv_exact(s, resp_len) - else: - s.sendall(body_bytes) - resp = recv_legacy_json(s) - - print(f"Response bytes: {len(resp)}") - print(f"Response head: {resp[:120].decode('utf-8', 'ignore')}") - - -if __name__ == "__main__": - main() From 8b327cba33dae884032ddfc662b3112418df9bed Mon Sep 17 00:00:00 2001 From: choej2303 Date: Sun, 14 Dec 2025 10:33:26 +0900 Subject: [PATCH 3/6] =?UTF-8?q?Refactor=20Python=20server=20architecture?= =?UTF-8?q?=20and=20stability=20/=20Python=20=EC=84=9C=EB=B2=84=20?= =?UTF-8?q?=EC=95=84=ED=82=A4=ED=85=8D=EC=B2=98=20=EB=B0=8F=20=EC=95=88?= =?UTF-8?q?=EC=A0=95=EC=84=B1=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MCPForUnity/Server~/mcp_wrapper.py | 121 ------ MCPForUnity/Server~/pyproject.toml | 5 +- MCPForUnity/Server~/src/main.py | 26 +- .../src/services/tools/manage_script.py | 61 +-- .../src/services/tools/script_apply_edits.py | 267 +----------- .../Server~/src/services/tools/utils.py | 406 ++++++++++++++++-- .../test_improved_anchor_matching.py | 45 +- MCPForUnity/Server~/wrapper.js | 49 ++- 8 files changed, 473 insertions(+), 507 deletions(-) delete mode 100644 MCPForUnity/Server~/mcp_wrapper.py diff --git a/MCPForUnity/Server~/mcp_wrapper.py b/MCPForUnity/Server~/mcp_wrapper.py deleted file mode 100644 index 8f725c418..000000000 --- a/MCPForUnity/Server~/mcp_wrapper.py +++ /dev/null @@ -1,121 +0,0 @@ -""" -MCP Wrapper for Antigravity on Windows -Fixes the "invalid trailing data" error by removing \r characters from stdout. - -This wrapper is needed because Windows adds \r\n line endings which cause -JSON-RPC parsing errors in Antigravity's MCP client. - -Usage in mcp_config.json: -{ - "mcpServers": { - "unity-mcp": { - "disabled": false, - "command": "python", - "args": [ - "C:\\path\\to\\this\\file\\mcp_wrapper.py" - ] - } - } -} - -Credits: Solution by @gajzzs from https://github.com/CoplayDev/unity-mcp/issues/430 -""" - -import sys -import os -import subprocess -import threading - -# Set binary mode for stdin/stdout to handle raw bytes -import msvcrt -msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) -msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) - - -def forward_stdin(proc): - """Forward stdin from Antigravity to the MCP server process.""" - try: - while True: - line = sys.stdin.buffer.readline() - if not line: - break - proc.stdin.write(line) - proc.stdin.flush() - except: - pass - - -def convert_stdout(proc): - """ - Read stdout from MCP server and remove \r characters before forwarding to Antigravity. - This fixes the "invalid trailing data" error caused by Windows line endings. - """ - try: - while True: - line = proc.stdout.readline() - if not line: - break - # Convert \r\n to \n by removing all \r - cleaned = line.replace(b'\r', b'') - sys.stdout.buffer.write(cleaned) - sys.stdout.buffer.flush() - except: - pass - - -# IMPORTANT: Update this path to match your uvx installation -# You can find the correct path by running: where uvx -# Common locations: -# - WinGet: C:\Users\\AppData\Local\Microsoft\WinGet\Packages\astral-sh.uv_Microsoft.Winget.Source_8wekyb3d8bbwe\uvx.exe -# - Manual install: C:\Users\\.cargo\bin\uvx.exe -# - System-wide: C:\Program Files\uv\uvx.exe - -# Try to detect uvx path automatically -username = os.environ.get('USERNAME') -uvx_path = None - -if username: - # Try WinGet installation path (most common on Windows) - winget_path = rf"C:\Users\{username}\AppData\Local\Microsoft\WinGet\Packages\astral-sh.uv_Microsoft.Winget.Source_8wekyb3d8bbwe\uvx.exe" - if os.path.exists(winget_path): - uvx_path = winget_path - -# Fallback to 'uvx' command (assumes it's in PATH) -if not uvx_path: - uvx_path = "uvx" - -# Allow custom Git repository URL via environment variable -# This makes it easy to use your own fork when pushing to Git -# Set UNITY_MCP_GIT_URL environment variable to override -# Example: UNITY_MCP_GIT_URL=git+https://github.com/YourUsername/unity-mcp@main#subdirectory=Server -default_git_url = "git+https://github.com/choej2303/unity-mcp-gg@versionUp#subdirectory=Server" -git_url = os.environ.get('UNITY_MCP_GIT_URL', default_git_url) - -# Support command-line argument for git URL (overrides environment variable) -# Usage: python mcp_wrapper.py [git_url] -if len(sys.argv) > 1: - git_url = sys.argv[1] - -cmd = [ - uvx_path, - "--from", git_url, - "mcp-for-unity", "--transport", "stdio" -] - -proc = subprocess.Popen( - cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL, - creationflags=subprocess.CREATE_NO_WINDOW if sys.platform == 'win32' else 0 -) - -t1 = threading.Thread(target=forward_stdin, args=(proc,), daemon=True) -t2 = threading.Thread(target=convert_stdout, args=(proc,), daemon=True) -t1.start() -t2.start() - -try: - sys.exit(proc.wait()) -except: - proc.terminate() diff --git a/MCPForUnity/Server~/pyproject.toml b/MCPForUnity/Server~/pyproject.toml index abc70b5ad..07b8db0db 100644 --- a/MCPForUnity/Server~/pyproject.toml +++ b/MCPForUnity/Server~/pyproject.toml @@ -20,7 +20,10 @@ dev = [ ] [project.scripts] -mcp-for-unity = "main:main" +mcp-for-unity = "src.main:main" + +[tool.setuptools.packages.find] +where = ["src"] [build-system] requires = ["setuptools>=80.9.0", "wheel>=0.45.1"] diff --git a/MCPForUnity/Server~/src/main.py b/MCPForUnity/Server~/src/main.py index eed455345..db5eb2868 100644 --- a/MCPForUnity/Server~/src/main.py +++ b/MCPForUnity/Server~/src/main.py @@ -65,13 +65,6 @@ "httpx", "urllib3", "mcp.server.lowlevel.server", - "uvicorn", - "uvicorn.access", - "uvicorn.error", - "docket", - "docket.worker", - "fastmcp", # <--- 🚨 범인 검거 - "starlette" # <--- 혹시 모를 공범 ): try: logging.getLogger(noisy).setLevel( @@ -412,9 +405,26 @@ def main(): logger.info(f"Starting FastMCP with HTTP transport on {host}:{port}") mcp.run(transport=transport, host=host, port=port) else: + # Use stdio transport for traditional MCP logger.info("Starting FastMCP with stdio transport") - mcp.run(transport='stdio', show_banner=False) + # 🚨 [CRITICAL] In STDIO mode only, suppress related loggers. + # Silence Uvicorn and related loggers to prevent stdout pollution. + # 🚨 [CRITICAL] Prevent stdout pollution in STDIO mode + for name in ( + "uvicorn", "uvicorn.error", "uvicorn.access", + "starlette", + "docket", "docket.worker", + "fastmcp", + ): + lg = logging.getLogger(name) + lg.setLevel(logging.WARNING) # ERROR if still too chatty + lg.propagate = False # prevent duplicate root logs + # If the rotating file handler was successfully created, attach it + if '_fh' in globals() and _fh not in lg.handlers: + lg.addHandler(_fh) + + mcp.run(transport='stdio') # Run the server diff --git a/MCPForUnity/Server~/src/services/tools/manage_script.py b/MCPForUnity/Server~/src/services/tools/manage_script.py index 8cbc1e9ea..074ef0bf7 100644 --- a/MCPForUnity/Server~/src/services/tools/manage_script.py +++ b/MCPForUnity/Server~/src/services/tools/manage_script.py @@ -1,7 +1,7 @@ import base64 import os from typing import Annotated, Any, Literal -from urllib.parse import urlparse, unquote + from fastmcp import FastMCP, Context @@ -11,56 +11,7 @@ import transport.legacy.unity_connection -def _split_uri(uri: str) -> tuple[str, str]: - """Split an incoming URI or path into (name, directory) suitable for Unity. - - Rules: - - unity://path/Assets/... → keep as Assets-relative (after decode/normalize) - - file://... → percent-decode, normalize, strip host and leading slashes, - then, if any 'Assets' segment exists, return path relative to that 'Assets' root. - Otherwise, fall back to original name/dir behavior. - - plain paths → decode/normalize separators; if they contain an 'Assets' segment, - return relative to 'Assets'. - """ - raw_path: str - if uri.startswith("unity://path/"): - raw_path = uri[len("unity://path/"):] - elif uri.startswith("file://"): - parsed = urlparse(uri) - host = (parsed.netloc or "").strip() - p = parsed.path or "" - # UNC: file://server/share/... -> //server/share/... - if host and host.lower() != "localhost": - p = f"//{host}{p}" - # Use percent-decoded path, preserving leading slashes - raw_path = unquote(p) - else: - raw_path = uri - - # Percent-decode any residual encodings and normalize separators - raw_path = unquote(raw_path).replace("\\", "/") - # Strip leading slash only for Windows drive-letter forms like "/C:/..." - if os.name == "nt" and len(raw_path) >= 3 and raw_path[0] == "/" and raw_path[2] == ":": - raw_path = raw_path[1:] - - # Normalize path (collapse ../, ./) - norm = os.path.normpath(raw_path).replace("\\", "/") - - # If an 'Assets' segment exists, compute path relative to it (case-insensitive) - parts = [p for p in norm.split("/") if p not in ("", ".")] - idx = next((i for i, seg in enumerate(parts) - if seg.lower() == "assets"), None) - assets_rel = "/".join(parts[idx:]) if idx is not None else None - - effective_path = assets_rel if assets_rel else norm - # For POSIX absolute paths outside Assets, drop the leading '/' - # to return a clean relative-like directory (e.g., '/tmp' -> 'tmp'). - if effective_path.startswith("/"): - effective_path = effective_path[1:] - - name = os.path.splitext(os.path.basename(effective_path))[0] - directory = os.path.dirname(effective_path) - return name, directory +from services.tools.utils import split_uri @mcp_for_unity_tool(description=( @@ -91,7 +42,7 @@ async def apply_text_edits( unity_instance = get_unity_instance_from_context(ctx) await ctx.info( f"Processing apply_text_edits: {uri} (unity_instance={unity_instance or 'default'})") - name, directory = _split_uri(uri) + name, directory = split_uri(uri) # Normalize common aliases/misuses for resilience: # - Accept LSP-style range objects: {range:{start:{line,character}, end:{...}}, newText|text} @@ -421,7 +372,7 @@ async def delete_script( unity_instance = get_unity_instance_from_context(ctx) await ctx.info( f"Processing delete_script: {uri} (unity_instance={unity_instance or 'default'})") - name, directory = _split_uri(uri) + name, directory = split_uri(uri) if not directory or directory.split("/")[0].lower() != "assets": return {"success": False, "code": "path_outside_assets", "message": "URI must resolve under 'Assets/'."} params = {"action": "delete", "name": name, "path": directory} @@ -446,7 +397,7 @@ async def validate_script( unity_instance = get_unity_instance_from_context(ctx) await ctx.info( f"Processing validate_script: {uri} (unity_instance={unity_instance or 'default'})") - name, directory = _split_uri(uri) + name, directory = split_uri(uri) if not directory or directory.split("/")[0].lower() != "assets": return {"success": False, "code": "path_outside_assets", "message": "URI must resolve under 'Assets/'."} if level not in ("basic", "standard"): @@ -584,7 +535,7 @@ async def get_sha( await ctx.info( f"Processing get_sha: {uri} (unity_instance={unity_instance or 'default'})") try: - name, directory = _split_uri(uri) + name, directory = split_uri(uri) params = {"action": "get_sha", "name": name, "path": directory} resp = await send_with_unity_instance( transport.legacy.unity_connection.async_send_command_with_retry, diff --git a/MCPForUnity/Server~/src/services/tools/script_apply_edits.py b/MCPForUnity/Server~/src/services/tools/script_apply_edits.py index c6cab187f..99417d062 100644 --- a/MCPForUnity/Server~/src/services/tools/script_apply_edits.py +++ b/MCPForUnity/Server~/src/services/tools/script_apply_edits.py @@ -7,203 +7,16 @@ from services.registry import mcp_for_unity_tool from services.tools import get_unity_instance_from_context -from services.tools.utils import parse_json_payload +from services.tools.utils import ( + parse_json_payload, + apply_edits_locally, + normalize_script_locator, + find_best_anchor_match +) from transport.unity_transport import send_with_unity_instance from transport.legacy.unity_connection import async_send_command_with_retry -async def _apply_edits_locally(original_text: str, edits: list[dict[str, Any]]) -> str: - text = original_text - for edit in edits or []: - op = ( - (edit.get("op") - or edit.get("operation") - or edit.get("type") - or edit.get("mode") - or "") - .strip() - .lower() - ) - - if not op: - allowed = "anchor_insert, prepend, append, replace_range, regex_replace" - raise RuntimeError( - f"op is required; allowed: {allowed}. Use 'op' (aliases accepted: type/mode/operation)." - ) - - if op == "prepend": - prepend_text = edit.get("text", "") - text = (prepend_text if prepend_text.endswith( - "\n") else prepend_text + "\n") + text - elif op == "append": - append_text = edit.get("text", "") - if not text.endswith("\n"): - text += "\n" - text += append_text - if not text.endswith("\n"): - text += "\n" - elif op == "anchor_insert": - anchor = edit.get("anchor", "") - position = (edit.get("position") or "before").lower() - insert_text = edit.get("text", "") - flags = re.MULTILINE | ( - re.IGNORECASE if edit.get("ignore_case") else 0) - - # Find the best match using improved heuristics - match = _find_best_anchor_match( - anchor, text, flags, bool(edit.get("prefer_last", True))) - if not match: - if edit.get("allow_noop", True): - continue - raise RuntimeError(f"anchor not found: {anchor}") - idx = match.start() if position == "before" else match.end() - text = text[:idx] + insert_text + text[idx:] - elif op == "replace_range": - start_line = int(edit.get("startLine", 1)) - start_col = int(edit.get("startCol", 1)) - end_line = int(edit.get("endLine", start_line)) - end_col = int(edit.get("endCol", 1)) - replacement = edit.get("text", "") - lines = text.splitlines(keepends=True) - max_line = len(lines) + 1 # 1-based, exclusive end - if (start_line < 1 or end_line < start_line or end_line > max_line - or start_col < 1 or end_col < 1): - raise RuntimeError("replace_range out of bounds") - - def index_of(line: int, col: int) -> int: - if line <= len(lines): - return sum(len(l) for l in lines[: line - 1]) + (col - 1) - return sum(len(l) for l in lines) - a = index_of(start_line, start_col) - b = index_of(end_line, end_col) - text = text[:a] + replacement + text[b:] - elif op == "regex_replace": - pattern = edit.get("pattern", "") - repl = edit.get("replacement", "") - # Translate $n backrefs (our input) to Python \g - repl_py = re.sub(r"\$(\d+)", r"\\g<\1>", repl) - count = int(edit.get("count", 0)) # 0 = replace all - flags = re.MULTILINE - if edit.get("ignore_case"): - flags |= re.IGNORECASE - text = re.sub(pattern, repl_py, text, count=count, flags=flags) - else: - allowed = "anchor_insert, prepend, append, replace_range, regex_replace" - raise RuntimeError( - f"unknown edit op: {op}; allowed: {allowed}. Use 'op' (aliases accepted: type/mode/operation).") - return text - - -def _find_best_anchor_match(pattern: str, text: str, flags: int, prefer_last: bool = True): - """ - Find the best anchor match using improved heuristics. - - For patterns like \\s*}\\s*$ that are meant to find class-ending braces, - this function uses heuristics to choose the most semantically appropriate match: - - 1. If prefer_last=True, prefer the last match (common for class-end insertions) - 2. Use indentation levels to distinguish class vs method braces - 3. Consider context to avoid matches inside strings/comments - - Args: - pattern: Regex pattern to search for - text: Text to search in - flags: Regex flags - prefer_last: If True, prefer the last match over the first - - Returns: - Match object of the best match, or None if no match found - """ - - # Find all matches - matches = list(re.finditer(pattern, text, flags)) - if not matches: - return None - - # If only one match, return it - if len(matches) == 1: - return matches[0] - - # For patterns that look like they're trying to match closing braces at end of lines - is_closing_brace_pattern = '}' in pattern and ( - '$' in pattern or pattern.endswith(r'\s*')) - - if is_closing_brace_pattern and prefer_last: - # Use heuristics to find the best closing brace match - return _find_best_closing_brace_match(matches, text) - - # Default behavior: use last match if prefer_last, otherwise first match - return matches[-1] if prefer_last else matches[0] - - -def _find_best_closing_brace_match(matches, text: str): - """ - Find the best closing brace match using C# structure heuristics. - - Enhanced heuristics for scope-aware matching: - 1. Prefer matches with lower indentation (likely class-level) - 2. Prefer matches closer to end of file - 3. Avoid matches that seem to be inside method bodies - 4. For #endregion patterns, ensure class-level context - 5. Validate insertion point is at appropriate scope - - Args: - matches: List of regex match objects - text: The full text being searched - - Returns: - The best match object - """ - if not matches: - return None - - scored_matches = [] - lines = text.splitlines() - - for match in matches: - score = 0 - start_pos = match.start() - - # Find which line this match is on - lines_before = text[:start_pos].count('\n') - line_num = lines_before - - if line_num < len(lines): - line_content = lines[line_num] - - # Calculate indentation level (lower is better for class braces) - indentation = len(line_content) - len(line_content.lstrip()) - - # Prefer lower indentation (class braces are typically less indented than method braces) - # Max 20 points for indentation=0 - score += max(0, 20 - indentation) - - # Prefer matches closer to end of file (class closing braces are typically at the end) - distance_from_end = len(lines) - line_num - # More points for being closer to end - score += max(0, 10 - distance_from_end) - - # Look at surrounding context to avoid method braces - context_start = max(0, line_num - 3) - context_end = min(len(lines), line_num + 2) - context_lines = lines[context_start:context_end] - - # Penalize if this looks like it's inside a method (has method-like patterns above) - for context_line in context_lines: - if re.search(r'\b(void|public|private|protected)\s+\w+\s*\(', context_line): - score -= 5 # Penalty for being near method signatures - - # Bonus if this looks like a class-ending brace (very minimal indentation and near EOF) - if indentation <= 4 and distance_from_end <= 3: - score += 15 # Bonus for likely class-ending brace - - scored_matches.append((score, match)) - - # Return the match with the highest score - scored_matches.sort(key=lambda x: x[0], reverse=True) - best_match = scored_matches[0][1] - - return best_match def _infer_class_name(script_name: str) -> str: @@ -220,61 +33,7 @@ def _extract_code_after(keyword: str, request: str) -> str: # Removed _is_structurally_balanced - validation now handled by C# side using Unity's compiler services -def _normalize_script_locator(name: str, path: str) -> tuple[str, str]: - """Best-effort normalization of script "name" and "path". - - Accepts any of: - - name = "SmartReach", path = "Assets/Scripts/Interaction" - - name = "SmartReach.cs", path = "Assets/Scripts/Interaction" - - name = "Assets/Scripts/Interaction/SmartReach.cs", path = "" - - path = "Assets/Scripts/Interaction/SmartReach.cs" (name empty) - - name or path using uri prefixes: unity://path/..., file://... - - accidental duplicates like "Assets/.../SmartReach.cs/SmartReach.cs" - - Returns (name_without_extension, directory_path_under_Assets). - """ - n = (name or "").strip() - p = (path or "").strip() - - def strip_prefix(s: str) -> str: - if s.startswith("unity://path/"): - return s[len("unity://path/"):] - if s.startswith("file://"): - return s[len("file://"):] - return s - - def collapse_duplicate_tail(s: str) -> str: - # Collapse trailing "/X.cs/X.cs" to "/X.cs" - parts = s.split("/") - if len(parts) >= 2 and parts[-1] == parts[-2]: - parts = parts[:-1] - return "/".join(parts) - - # Prefer a full path if provided in either field - candidate = "" - for v in (n, p): - v2 = strip_prefix(v) - if v2.endswith(".cs") or v2.startswith("Assets/"): - candidate = v2 - break - - if candidate: - candidate = collapse_duplicate_tail(candidate) - # If a directory was passed in path and file in name, join them - if not candidate.endswith(".cs") and n.endswith(".cs"): - v2 = strip_prefix(n) - candidate = (candidate.rstrip("/") + "/" + v2.split("/")[-1]) - if candidate.endswith(".cs"): - parts = candidate.split("/") - file_name = parts[-1] - dir_path = "/".join(parts[:-1]) if len(parts) > 1 else "Assets" - base = file_name[:- - 3] if file_name.lower().endswith(".cs") else file_name - return base, dir_path - - # Fall back: remove extension from name if present and return given path - base_name = n[:-3] if n.lower().endswith(".cs") else n - return base_name, (p or "Assets") + def _with_norm(resp: dict[str, Any] | Any, edits: list[dict[str, Any]], routing: str | None = None) -> dict[str, Any] | Any: @@ -379,7 +138,7 @@ async def script_apply_edits( return {"success": False, "message": f"Edits must be a list or JSON string of a list, got {type(edits)}"} # Normalize locator first so downstream calls target the correct script file. - name, path = _normalize_script_locator(name, path) + name, path = normalize_script_locator(name, path) # Normalize unsupported or aliased ops to known structured/text paths def _unwrap_and_alias(edit: dict[str, Any]) -> dict[str, Any]: @@ -658,7 +417,7 @@ def line_col_from_index(idx: int) -> tuple[int, int]: re.IGNORECASE if e.get("ignore_case") else 0) try: # Use improved anchor matching logic - m = _find_best_anchor_match( + m = find_best_anchor_match( anchor, base_text, flags, prefer_last=True) except Exception as ex: return _with_norm(_err("bad_regex", f"Invalid anchor regex: {ex}", normalized=normalized_for_echo, routing="mixed/text-first", extra={"hint": "Escape parentheses/braces or use a simpler anchor."}), normalized_for_echo, routing="mixed/text-first") @@ -807,7 +566,7 @@ def line_col_from_index(idx: int) -> tuple[int, int]: try: flags = re.MULTILINE | ( re.IGNORECASE if e.get("ignore_case") else 0) - m = _find_best_anchor_match( + m = find_best_anchor_match( anchor, base_text, flags, prefer_last=True) except Exception as ex: return _with_norm(_err("bad_regex", f"Invalid anchor regex: {ex}", normalized=normalized_for_echo, routing="text", extra={"hint": "Escape parentheses/braces or use a simpler anchor."}), normalized_for_echo, routing="text") @@ -852,7 +611,7 @@ def line_col_from_index(idx: int) -> tuple[int, int]: except Exception as ex: return _with_norm(_err("bad_regex", f"Invalid regex pattern: {ex}", normalized=normalized_for_echo, routing="text", extra={"hint": "Escape special chars or prefer structured delete for methods."}), normalized_for_echo, routing="text") # Use smart anchor matching for consistent behavior with anchor_insert - m = _find_best_anchor_match( + m = find_best_anchor_match( pattern, base_text, flags, prefer_last=True) if not m: continue @@ -914,7 +673,7 @@ def _expand_dollars(rep: str, _m=m) -> str: # If confirm=false (default) and preview not requested, return diff and instruct confirm=true to apply. if "regex_replace" in text_ops and (preview or not (options or {}).get("confirm")): try: - preview_text = _apply_edits_locally(contents, edits) + preview_text = apply_edits_locally(contents, edits) import difflib diff = list(difflib.unified_diff(contents.splitlines( ), preview_text.splitlines(), fromfile="before", tofile="after", n=2)) @@ -927,7 +686,7 @@ def _expand_dollars(rep: str, _m=m) -> str: return _with_norm({"success": False, "code": "preview_failed", "message": f"Preview failed: {e}"}, normalized_for_echo, routing="text") # 2) apply edits locally (only if not text-ops) try: - new_contents = _apply_edits_locally(contents, edits) + new_contents = apply_edits_locally(contents, edits) except Exception as e: return {"success": False, "message": f"Edit application failed: {e}"} diff --git a/MCPForUnity/Server~/src/services/tools/utils.py b/MCPForUnity/Server~/src/services/tools/utils.py index ad5d8970c..b6fb1e0ca 100644 --- a/MCPForUnity/Server~/src/services/tools/utils.py +++ b/MCPForUnity/Server~/src/services/tools/utils.py @@ -1,60 +1,378 @@ -"""Shared helper utilities for MCP server tools.""" - -from __future__ import annotations - import json +import re +import os from typing import Any +from urllib.parse import urlparse, unquote +import math -_TRUTHY = {"true", "1", "yes", "on"} -_FALSY = {"false", "0", "no", "off"} -def coerce_bool(value: Any, default: bool | None = None) -> bool | None: - """Attempt to coerce a loosely-typed value to a boolean.""" - if value is None: - return default +def parse_json_payload(payload: str | Any) -> Any: + """Helper to robustly parse a potentially stringified JSON payload.""" + if not isinstance(payload, str): + return payload + + # Check if it looks like JSON structure + stripped = payload.strip() + if not (stripped.startswith("{") or stripped.startswith("[")): + return payload + + try: + return json.loads(payload) + except (json.JSONDecodeError, ValueError): + # If parsing fails, assume it was meant to be a literal string + return payload + + +def coerce_bool(value: Any, default: bool = False) -> bool: + """Coerce various input types to boolean.""" if isinstance(value, bool): return value if isinstance(value, str): - lowered = value.strip().lower() - if lowered in _TRUTHY: - return True - if lowered in _FALSY: - return False - return default - return bool(value) + return value.lower() in ("true", "1", "yes", "on", "y") + if isinstance(value, (int, float)): + return value != 0 + return default -def parse_json_payload(value: Any) -> Any: +def coerce_vec3(value: Any, default: list[float] | None = None) -> list[float] | None: + """ + Attempt to coerce a value into a [x, y, z] float list. + + Accepts: + - List/Tuple of 3 numbers + - JSON string "[x, y, z]" + - Comma/space separated string "x, y, z" or "x y z" """ - Attempt to parse a value that might be a JSON string into its native object. + if value is None: + return default - This is a tolerant parser used to handle cases where MCP clients or LLMs - serialize complex objects (lists, dicts) into strings. It also handles - scalar values like numbers, booleans, and null. + # First try to parse if it's a string that looks like JSON + val = parse_json_payload(value) - Args: - value: The input value (can be str, list, dict, etc.) + def _to_vec3(parts): + try: + vec = [float(parts[0]), float(parts[1]), float(parts[2])] + except (ValueError, TypeError, IndexError): + return default + return vec if all(math.isfinite(n) for n in vec) else default + if isinstance(val, (list, tuple)) and len(val) == 3: + return _to_vec3(val) + + # Handle legacy strings "1,2,3" or "1 2 3" + if isinstance(val, str): + s = val.strip() + # minimal tolerant parse for "[x,y,z]" or "x,y,z" + if s.startswith("[") and s.endswith("]"): + s = s[1:-1] + # support "x,y,z" and "x y z" + parts = [p.strip() for p in (s.split(",") if "," in s else s.split())] + if len(parts) == 3: + return _to_vec3(parts) + + return default + + +# --- Edit Utils (Moved from edit_utils.py) --- + +def split_uri(uri: str) -> tuple[str, str]: + """Split an incoming URI or path into (name, directory) suitable for Unity. + + Rules: + - unity://path/Assets/... → keep as Assets-relative (after decode/normalize) + - file://... → percent-decode, normalize, strip host and leading slashes, + then, if any 'Assets' segment exists, return path relative to that 'Assets' root. + Otherwise, fall back to original name/dir behavior. + - plain paths → decode/normalize separators; if they contain an 'Assets' segment, + return relative to 'Assets'. + """ + raw_path: str + if uri.startswith("unity://path/"): + raw_path = uri[len("unity://path/"):] + elif uri.startswith("file://"): + parsed = urlparse(uri) + host = (parsed.netloc or "").strip() + p = parsed.path or "" + # UNC: file://server/share/... -> //server/share/... + if host and host.lower() != "localhost": + p = f"//{host}{p}" + # Use percent-decoded path, preserving leading slashes + raw_path = unquote(p) + else: + raw_path = uri + + # Percent-decode any residual encodings and normalize separators + raw_path = unquote(raw_path).replace("\\", "/") + # Strip leading slash only for Windows drive-letter forms like "/C:/..." + if os.name == "nt" and len(raw_path) >= 3 and raw_path[0] == "/" and raw_path[2] == ":": + raw_path = raw_path[1:] + + # Normalize path (collapse ../, ./) + norm = os.path.normpath(raw_path).replace("\\", "/") + + # If an 'Assets' segment exists, compute path relative to it (case-insensitive) + parts = [p for p in norm.split("/") if p not in ("", ".")] + idx = next((i for i, seg in enumerate(parts) + if seg.lower() == "assets"), None) + assets_rel = "/".join(parts[idx:]) if idx is not None else None + + effective_path = assets_rel if assets_rel else norm + # For POSIX absolute paths outside Assets, drop the leading '/' + # to return a clean relative-like directory (e.g., '/tmp' -> 'tmp'). + if effective_path.startswith("/"): + effective_path = effective_path[1:] + + name = os.path.splitext(os.path.basename(effective_path))[0] + directory = os.path.dirname(effective_path) + return name, directory + + +def normalize_script_locator(name: str, path: str) -> tuple[str, str]: + """Best-effort normalization of script "name" and "path". + + Accepts any of: + - name = "SmartReach", path = "Assets/Scripts/Interaction" + - name = "SmartReach.cs", path = "Assets/Scripts/Interaction" + - name = "Assets/Scripts/Interaction/SmartReach.cs", path = "" + - path = "Assets/Scripts/Interaction/SmartReach.cs" (name empty) + - name or path using uri prefixes: unity://path/..., file://... + - accidental duplicates like "Assets/.../SmartReach.cs/SmartReach.cs" + + Returns (name_without_extension, directory_path_under_Assets). + """ + n = (name or "").strip() + p = (path or "").strip() + + def strip_prefix(s: str) -> str: + if s.startswith("unity://path/"): + return s[len("unity://path/"):] + if s.startswith("file://"): + return s[len("file://"):] + return s + + def collapse_duplicate_tail(s: str) -> str: + # Collapse trailing "/X.cs/X.cs" to "/X.cs" + parts = s.split("/") + if len(parts) >= 2 and parts[-1] == parts[-2]: + parts = parts[:-1] + return "/".join(parts) + + # Prefer a full path if provided in either field + candidate = "" + for v in (n, p): + v2 = strip_prefix(v) + if v2.endswith(".cs") or v2.startswith("Assets/"): + candidate = v2 + break + + if candidate: + candidate = collapse_duplicate_tail(candidate) + # If a directory was passed in path and file in name, join them + if not candidate.endswith(".cs") and n.endswith(".cs"): + v2 = strip_prefix(n) + candidate = (candidate.rstrip("/") + "/" + v2.split("/")[-1]) + if candidate.endswith(".cs"): + parts = candidate.split("/") + file_name = parts[-1] + dir_path = "/".join(parts[:-1]) if len(parts) > 1 else "Assets" + base = file_name[:- + 3] if file_name.lower().endswith(".cs") else file_name + return base, dir_path + + # Fall back: remove extension from name if present and return given path + base_name = n[:-3] if n.lower().endswith(".cs") else n + return base_name, (p or "Assets") + + +def apply_edits_locally(original_text: str, edits: list[dict[str, Any]]) -> str: + text = original_text + for edit in edits or []: + op = ( + (edit.get("op") + or edit.get("operation") + or edit.get("type") + or edit.get("mode") + or "") + .strip() + .lower() + ) + + if not op: + allowed = "anchor_insert, prepend, append, replace_range, regex_replace" + raise RuntimeError( + f"op is required; allowed: {allowed}. Use 'op' (aliases accepted: type/mode/operation)." + ) + + if op == "prepend": + prepend_text = edit.get("text", "") + text = (prepend_text if prepend_text.endswith( + "\n") else prepend_text + "\n") + text + elif op == "append": + append_text = edit.get("text", "") + if not text.endswith("\n"): + text += "\n" + text += append_text + if not text.endswith("\n"): + text += "\n" + elif op == "anchor_insert": + anchor = edit.get("anchor", "") + position = (edit.get("position") or "before").lower() + insert_text = edit.get("text", "") + flags = re.MULTILINE | ( + re.IGNORECASE if edit.get("ignore_case") else 0) + + # Find the best match using improved heuristics + match = find_best_anchor_match( + anchor, text, flags, bool(edit.get("prefer_last", True))) + if not match: + if edit.get("allow_noop", True): + continue + raise RuntimeError(f"anchor not found: {anchor}") + idx = match.start() if position == "before" else match.end() + text = text[:idx] + insert_text + text[idx:] + elif op == "replace_range": + start_line = int(edit.get("startLine", 1)) + start_col = int(edit.get("startCol", 1)) + end_line = int(edit.get("endLine", start_line)) + end_col = int(edit.get("endCol", 1)) + replacement = edit.get("text", "") + lines = text.splitlines(keepends=True) + max_line = len(lines) + 1 # 1-based, exclusive end + if (start_line < 1 or end_line < start_line or end_line > max_line + or start_col < 1 or end_col < 1): + raise RuntimeError("replace_range out of bounds") + + def index_of(line: int, col: int, lines_ref: list[str] = lines) -> int: + if line <= len(lines_ref): + return sum(len(ln) for ln in lines_ref[: line - 1]) + (col - 1) + return sum(len(ln) for ln in lines_ref) + a = index_of(start_line, start_col) + b = index_of(end_line, end_col) + text = text[:a] + replacement + text[b:] + elif op == "regex_replace": + pattern = edit.get("pattern", "") + repl = edit.get("replacement", "") + # Translate $n backrefs (our input) to Python \g + repl_py = re.sub(r"\$(\d+)", r"\\g<\1>", repl) + count = int(edit.get("count", 0)) # 0 = replace all + flags = re.MULTILINE + if edit.get("ignore_case"): + flags |= re.IGNORECASE + text = re.sub(pattern, repl_py, text, count=count, flags=flags) + else: + allowed = "anchor_insert, prepend, append, replace_range, regex_replace" + raise RuntimeError( + f"unknown edit op: {op}; allowed: {allowed}. Use 'op' (aliases accepted: type/mode/operation).") + return text + + +def find_best_anchor_match(pattern: str, text: str, flags: int, prefer_last: bool = True): + """ + Find the best anchor match using improved heuristics. + + For patterns like \\s*}\\s*$ that are meant to find class-ending braces, + this function uses heuristics to choose the most semantically appropriate match: + + 1. If prefer_last=True, prefer the last match (common for class-end insertions) + 2. Use indentation levels to distinguish class vs method braces + 3. Consider context to avoid matches inside strings/comments + + Args: + pattern: Regex pattern to search for + text: Text to search in + flags: Regex flags + prefer_last: If True, prefer the last match over the first + Returns: - The parsed JSON object/list if the input was a valid JSON string, - or the original value if parsing failed or wasn't necessary. + Match object of the best match, or None if no match found """ - if not isinstance(value, str): - return value - - val_trimmed = value.strip() - - # Fast path: if it doesn't look like JSON structure, return as is - if not ( - (val_trimmed.startswith("{") and val_trimmed.endswith("}")) or - (val_trimmed.startswith("[") and val_trimmed.endswith("]")) or - val_trimmed in ("true", "false", "null") or - (val_trimmed.replace(".", "", 1).replace("-", "", 1).isdigit()) - ): - return value - try: - return json.loads(value) - except (json.JSONDecodeError, ValueError): - # If parsing fails, assume it was meant to be a literal string - return value + # Find all matches + matches = list(re.finditer(pattern, text, flags)) + if not matches: + return None + + # If only one match, return it + if len(matches) == 1: + return matches[0] + + # For patterns that look like they're trying to match closing braces at end of lines + is_closing_brace_pattern = '}' in pattern and ( + '$' in pattern or pattern.endswith(r'\s*')) + + if is_closing_brace_pattern and prefer_last: + # Use heuristics to find the best closing brace match + return _find_best_closing_brace_match(matches, text) + + # Default behavior: use last match if prefer_last, otherwise first match + return matches[-1] if prefer_last else matches[0] + + +def _find_best_closing_brace_match(matches, text: str): + """ + Find the best closing brace match using C# structure heuristics. + + Enhanced heuristics for scope-aware matching: + 1. Prefer matches with lower indentation (likely class-level) + 2. Prefer matches closer to end of file + 3. Avoid matches that seem to be inside method bodies + 4. For #endregion patterns, ensure class-level context + 5. Validate insertion point is at appropriate scope + + Args: + matches: List of regex match objects + text: The full text being searched + + Returns: + The best match object + """ + if not matches: + return None + + scored_matches = [] + lines = text.splitlines() + + for match in matches: + score = 0 + start_pos = match.start() + + # Find which line this match is on + lines_before = text[:start_pos].count('\n') + line_num = lines_before + + if line_num < len(lines): + line_content = lines[line_num] + + # Calculate indentation level (lower is better for class braces) + indentation = len(line_content) - len(line_content.lstrip()) + + # Prefer lower indentation (class braces are typically less indented than method braces) + # Max 20 points for indentation=0 + score += max(0, 20 - indentation) + + # Prefer matches closer to end of file (class closing braces are typically at the end) + distance_from_end = len(lines) - line_num + # More points for being closer to end + score += max(0, 10 - distance_from_end) + + # Look at surrounding context to avoid method braces + context_start = max(0, line_num - 3) + context_end = min(len(lines), line_num + 2) + context_lines = lines[context_start:context_end] + + # Penalize if this looks like it's inside a method (has method-like patterns above) + for context_line in context_lines: + if re.search(r'\b(void|public|private|protected)\s+\w+\s*\(', context_line): + score -= 5 # Penalty for being near method signatures + + # Bonus if this looks like a class-ending brace (very minimal indentation and near EOF) + if indentation <= 4 and distance_from_end <= 3: + score += 15 # Bonus for likely class-ending brace + + scored_matches.append((score, match)) + + # Return the match with the highest score + scored_matches.sort(key=lambda x: x[0], reverse=True) + best_match = scored_matches[0][1] + + return best_match diff --git a/MCPForUnity/Server~/tests/integration/test_improved_anchor_matching.py b/MCPForUnity/Server~/tests/integration/test_improved_anchor_matching.py index 19a49ae4b..eedd92cc2 100644 --- a/MCPForUnity/Server~/tests/integration/test_improved_anchor_matching.py +++ b/MCPForUnity/Server~/tests/integration/test_improved_anchor_matching.py @@ -3,11 +3,8 @@ """ import re - import pytest - -import services.tools.script_apply_edits as script_apply_edits_module - +import services.tools.utils as utils_module def test_improved_anchor_matching(): """Test that our improved anchor matching finds the right closing brace.""" @@ -32,7 +29,7 @@ def test_improved_anchor_matching(): flags = re.MULTILINE # Test our improved function - best_match = script_apply_edits_module._find_best_anchor_match( + best_match = utils_module.find_best_anchor_match( anchor_pattern, test_code, flags, prefer_last=True ) @@ -81,7 +78,7 @@ def test_old_vs_new_matching(): '\n') + 1 if old_match else None # New behavior (improved matching) - new_match = script_apply_edits_module._find_best_anchor_match( + new_match = utils_module.find_best_anchor_match( anchor_pattern, test_code, flags, prefer_last=True ) new_line = test_code[:new_match.start()].count( @@ -94,9 +91,8 @@ def test_old_vs_new_matching(): 2, f"expected class-end match near end (>= {total_lines-2}), got {new_line}" -@pytest.mark.asyncio -async def test_apply_edits_with_improved_matching(): - """Test that _apply_edits_locally uses improved matching.""" +def test_apply_edits_with_improved_matching(): + """Test that apply_edits_locally uses improved matching.""" original_code = '''using UnityEngine; @@ -118,8 +114,9 @@ async def test_apply_edits_with_improved_matching(): "text": "\n public void NewMethod() { Debug.Log(\"Added at class end\"); }\n" }] - result = await script_apply_edits_module._apply_edits_locally( - original_code, edits) + # Now synchronous call + result = utils_module.apply_edits_locally(original_code, edits) + lines = result.split('\n') try: idx = next(i for i, line in enumerate(lines) if "NewMethod" in line) @@ -134,15 +131,33 @@ async def test_apply_edits_with_improved_matching(): print("Testing improved anchor matching...") print("="*60) - success1 = test_improved_anchor_matching() + try: + test_improved_anchor_matching() + print("test_improved_anchor_matching passed") + success1 = True + except Exception as e: + print(f"test_improved_anchor_matching failed: {e}") + success1 = False print("\n" + "="*60) print("Comparing old vs new behavior...") - success2 = test_old_vs_new_matching() + try: + test_old_vs_new_matching() + print("test_old_vs_new_matching passed") + success2 = True + except Exception as e: + print(f"test_old_vs_new_matching failed: {e}") + success2 = False print("\n" + "="*60) - print("Testing _apply_edits_locally with improved matching...") - success3 = test_apply_edits_with_improved_matching() + print("Testing apply_edits_locally with improved matching...") + try: + test_apply_edits_with_improved_matching() + print("test_apply_edits_with_improved_matching passed") + success3 = True + except Exception as e: + print(f"test_apply_edits_with_improved_matching failed: {e}") + success3 = False print("\n" + "="*60) if success1 and success2 and success3: diff --git a/MCPForUnity/Server~/wrapper.js b/MCPForUnity/Server~/wrapper.js index 2350bc6f3..2d69f4016 100644 --- a/MCPForUnity/Server~/wrapper.js +++ b/MCPForUnity/Server~/wrapper.js @@ -17,20 +17,49 @@ log("Wrapper started"); const serverDir = __dirname; +// Construct updated PATH with common uv locations +const localAppData = process.env.LOCALAPPDATA || ""; +const userProfile = process.env.USERPROFILE || ""; +const homeDir = process.env.HOME || process.env.USERPROFILE || ""; + +const extraPaths = + process.platform === "win32" + ? [ + path.join(localAppData, "Programs", "uv"), // uv standalone + path.join(localAppData, "uv"), // Alternative + path.join(userProfile, ".cargo", "bin"), // Cargo install + path.join(localAppData, "bin"), + ] + : [ + path.join(homeDir, ".cargo", "bin"), + path.join(homeDir, ".local", "bin"), + "/usr/local/bin", + "/opt/homebrew/bin", // macOS Homebrew + ]; + +const newPath = + extraPaths + .filter((p) => (p && !p.startsWith(path.sep)) || p.length > 1) + .join(path.delimiter) + + path.delimiter + + (process.env.PATH || ""); + // Use uv run with --quiet to minimize noise -// Add --quiet to uv run commands to suppress "resolved ..." messages const pythonProcess = spawn( "uv", ["run", "--quiet", "src/main.py", "--transport", "stdio"], { cwd: serverDir, stdio: ["pipe", "pipe", "pipe"], - shell: true, // Needed for windows command resolution sometimes + shell: true, env: { ...process.env, + PATH: newPath, // Inject updated PATH PYTHONUNBUFFERED: "1", PYTHONIOENCODING: "utf-8", - HOME: process.env.USERPROFILE, // Ensure uv finds home on Windows + ...(process.platform === "win32" && userProfile + ? { HOME: userProfile } + : {}), }, } ); @@ -51,8 +80,7 @@ pythonProcess.stdout.on("data", (data) => { if (!line) continue; try { - // Validate JSON. If strictly JSON-RPC, it must be an object. - // We don't parse fully to save perf, but JSON.parse ensures validity. + // Validate JSON by parsing. Result is discarded; we only need to verify it's valid JSON. JSON.parse(line); // If valid, pass to stdout with a clean newline @@ -90,11 +118,14 @@ process.stdin.pipe(pythonProcess.stdin); function cleanup() { if (pythonProcess) { try { - pythonProcess.kill(); if (process.platform === "win32") { - require("child_process").execSync( - `taskkill /pid ${pythonProcess.pid} /T /F` - ); + if (pythonProcess.pid) { + require("child_process").execSync( + `taskkill /pid ${pythonProcess.pid} /T /F` + ); + } + } else { + pythonProcess.kill(); } } catch (e) { /* ignore */ From f8f809120da29a844650e0627e4156a9a40a0379 Mon Sep 17 00:00:00 2001 From: choej2303 Date: Sun, 14 Dec 2025 10:34:27 +0900 Subject: [PATCH 4/6] =?UTF-8?q?Improve=20Unity=20Editor=20integration=20an?= =?UTF-8?q?d=20fix=20concurrency=20issues=20/=20Unity=20=EC=97=90=EB=94=94?= =?UTF-8?q?=ED=84=B0=20=ED=86=B5=ED=95=A9=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F?= =?UTF-8?q?=20=EB=8F=99=EC=8B=9C=EC=84=B1=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/python-tests.yml | 2 +- .../Clients/McpClientConfiguratorBase.cs | 290 +++++++---- .../Editor/Constants/EditorPrefKeys.cs | 23 +- MCPForUnity/Editor/Data/.keep | 0 .../Editor/Dependencies/DependencyManager.cs | 86 ++- .../PlatformDetectors/IPlatformDetector.cs | 9 +- .../LinuxPlatformDetector.cs | 43 +- .../MacOSPlatformDetector.cs | 80 ++- .../PlatformDetectors/PlatformDetectorBase.cs | 247 +++++++-- .../WindowsPlatformDetector.cs | 277 +++++----- .../Editor/Helpers/AssetPathUtility.cs | 146 +++++- .../Editor/Helpers/ConfigJsonBuilder.cs | 67 ++- MCPForUnity/Editor/Helpers/ExecPath.cs | 36 +- MCPForUnity/Editor/MCPForUnity.Editor.asmdef | 4 +- .../Editor/MenuItems/MCPForUnityMenu.cs | 22 +- .../Services/ClientConfigurationService.cs | 2 + .../Editor/Services/IPathResolverService.cs | 44 ++ .../Editor/Services/PathResolverService.cs | 164 +++--- .../Services/ServerManagementService.cs | 97 +++- .../Editor/Services/ToolDiscoveryService.cs | 13 +- .../Transport/Transports/StdioBridgeHost.cs | 46 +- .../Editor/Setup/ServerEnvironmentSetup.cs | 327 ++++++++++++ .../Setup/ServerEnvironmentSetup.cs.meta | 11 + .../ClientConfig/McpClientConfigSection.cs | 87 +--- .../Editor/Windows/Components/Common.uss | 164 +++++- .../Components/Settings/McpSettingsSection.cs | 228 +------- .../Settings/McpSettingsSection.uxml | 44 -- .../Editor/Windows/Components/Setup.meta | 8 + .../Components/Setup/McpSetupSection.cs | 492 ++++++++++++++++++ .../Components/Setup/McpSetupSection.cs.meta | 11 + .../Components/Setup/McpSetupSection.uxml | 117 +++++ .../Setup/McpSetupSection.uxml.meta | 10 + .../Editor/Windows/MCPForUnityEditorWindow.cs | 402 ++++++++------ .../Windows/MCPForUnityEditorWindow.uss | 127 ++++- .../Windows/MCPForUnityEditorWindow.uxml | 54 +- .../Editor/Windows/MCPSetupWindow.uxml | 2 +- MCPForUnity/package.json | 3 +- 37 files changed, 2765 insertions(+), 1020 deletions(-) create mode 100644 MCPForUnity/Editor/Data/.keep create mode 100644 MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs create mode 100644 MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs.meta create mode 100644 MCPForUnity/Editor/Windows/Components/Setup.meta create mode 100644 MCPForUnity/Editor/Windows/Components/Setup/McpSetupSection.cs create mode 100644 MCPForUnity/Editor/Windows/Components/Setup/McpSetupSection.cs.meta create mode 100644 MCPForUnity/Editor/Windows/Components/Setup/McpSetupSection.uxml create mode 100644 MCPForUnity/Editor/Windows/Components/Setup/McpSetupSection.uxml.meta diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index f94f22758..34cb31272 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -22,7 +22,7 @@ jobs: version: "latest" - name: Set up Python - run: uv python install 3.10 + run: uv python install 3.11 - name: Install dependencies run: | diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs index f1dc2eb98..852a9202c 100644 --- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs +++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs @@ -102,6 +102,8 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) string configuredUrl = null; bool configExists = false; + string command = null; + if (client.IsVsCodeLayout) { var vsConfig = JsonConvert.DeserializeObject(configJson) as JObject; @@ -114,12 +116,8 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) if (unityToken is JObject unityObj) { configExists = true; - - var argsToken = unityObj["args"]; - if (argsToken is JArray) - { - args = argsToken.ToObject(); - } + command = ExtractCommand(unityObj); + args = ExtractArgs(unityObj); var urlToken = unityObj["url"] ?? unityObj["serverUrl"]; if (urlToken != null && urlToken.Type != JTokenType.Null) @@ -131,11 +129,22 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) } else { - McpConfig standardConfig = JsonConvert.DeserializeObject(configJson); - if (standardConfig?.mcpServers?.unityMCP != null) + // Use JObject parsing for flexibility (handling both url and serverUrl without strict model dependency) + var rootObj = JsonConvert.DeserializeObject(configJson) as JObject; + var unityToken = rootObj?["mcpServers"]?["unityMCP"]; + + if (unityToken is JObject unityObj) { - args = standardConfig.mcpServers.unityMCP.args; configExists = true; + command = ExtractCommand(unityObj); + args = ExtractArgs(unityObj); + + // Check for both 'url' (standard) and 'serverUrl' (Antigravity) + var urlToken = unityObj["url"] ?? unityObj["serverUrl"]; + if (urlToken != null && urlToken.Type != JTokenType.Null) + { + configuredUrl = urlToken.ToString(); + } } } @@ -146,17 +155,44 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) } bool matches = false; - if (args != null && args.Length > 0) + bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); + + if (useHttpTransport) { - string expectedUvxUrl = AssetPathUtility.GetMcpServerGitUrl(); - string configuredUvxUrl = McpConfigurationHelper.ExtractUvxUrl(args); - matches = !string.IsNullOrEmpty(configuredUvxUrl) && - McpConfigurationHelper.PathsEqual(configuredUvxUrl, expectedUvxUrl); + // Check: HTTP (Only check this if we are in HTTP mode) + if (!string.IsNullOrEmpty(configuredUrl)) + { + string expectedUrl = HttpEndpointUtility.GetMcpRpcUrl(); + matches = UrlsEqual(configuredUrl, expectedUrl); + } } - else if (!string.IsNullOrEmpty(configuredUrl)) + else { - string expectedUrl = HttpEndpointUtility.GetMcpRpcUrl(); - matches = UrlsEqual(configuredUrl, expectedUrl); + // Check: Node.js Wrapper (Stdio) (Only check this if we are in Stdio mode) + string expectedWrapper = AssetPathUtility.GetWrapperJsPath(); + if (!string.IsNullOrEmpty(command) && args != null && args.Length > 0 && !string.IsNullOrEmpty(expectedWrapper)) + { + try + { + if (Path.GetFileNameWithoutExtension(command).Equals("node", StringComparison.OrdinalIgnoreCase) && + McpConfigurationHelper.PathsEqual(args[0], expectedWrapper)) + { + matches = true; + } + } + catch (ArgumentException) + { + // Invalid command path, skip Node.js wrapper check + } + } + // Check: UVX (Stdio - Legacy/Fallback) + else if (args != null && args.Length > 0) + { + string expectedUvxUrl = AssetPathUtility.GetMcpServerGitUrl(); + string configuredUvxUrl = McpConfigurationHelper.ExtractUvxUrl(args); + matches = !string.IsNullOrEmpty(configuredUvxUrl) && + McpConfigurationHelper.PathsEqual(configuredUvxUrl, expectedUvxUrl); + } } if (matches) @@ -190,6 +226,26 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) return client.status; } + private static string ExtractCommand(JObject configObj) + { + var commandToken = configObj["command"]; + if (commandToken != null && commandToken.Type == JTokenType.String) + { + return commandToken.ToString(); + } + return null; + } + + private static string[] ExtractArgs(JObject configObj) + { + var argsToken = configObj["args"]; + if (argsToken is JArray) + { + return argsToken.ToObject(); + } + return null; + } + public override void Configure() { string path = GetConfigPath(); @@ -327,6 +383,9 @@ public override string GetManualSnippet() /// CLI-based configurator (Claude Code). public abstract class ClaudeCliMcpConfigurator : McpClientConfiguratorBase { + private static readonly object _claudeCliLock = new object(); + private static bool _isClaudeCliRunning = false; + public ClaudeCliMcpConfigurator(McpClient client) : base(client) { } public override bool SupportsAutoConfigure => true; @@ -349,28 +408,7 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true) string args = "mcp list"; string projectDir = Path.GetDirectoryName(Application.dataPath); - - string pathPrepend = null; - if (Application.platform == RuntimePlatform.OSXEditor) - { - pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"; - } - else if (Application.platform == RuntimePlatform.LinuxEditor) - { - pathPrepend = "/usr/local/bin:/usr/bin:/bin"; - } - - try - { - string claudeDir = Path.GetDirectoryName(claudePath); - if (!string.IsNullOrEmpty(claudeDir)) - { - pathPrepend = string.IsNullOrEmpty(pathPrepend) - ? claudeDir - : $"{claudeDir}:{pathPrepend}"; - } - } - catch { } + string pathPrepend = BuildPathPrepend(claudePath); if (ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out _, 10000, pathPrepend)) { @@ -405,84 +443,127 @@ public override void Configure() private void Register() { - var pathService = MCPServiceLocator.Paths; - string claudePath = pathService.GetClaudeCliPath(); - if (string.IsNullOrEmpty(claudePath)) + lock (_claudeCliLock) { - throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first."); + if (_isClaudeCliRunning) + { + throw new InvalidOperationException("Claude CLI operation already in progress. Please wait."); + } + _isClaudeCliRunning = true; } + + try + { + var pathService = MCPServiceLocator.Paths; + string claudePath = pathService.GetClaudeCliPath(); + if (string.IsNullOrEmpty(claudePath)) + { + throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first."); + } - bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); + bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); - string args; - if (useHttpTransport) - { - string httpUrl = HttpEndpointUtility.GetMcpRpcUrl(); - args = $"mcp add --transport http UnityMCP {httpUrl}"; - } - else - { - var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts(); - args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" --from \"{gitUrl}\" {packageName}"; - } + string args; + if (useHttpTransport) + { + string httpUrl = HttpEndpointUtility.GetMcpRpcUrl(); + args = $"mcp add --transport http UnityMCP {httpUrl}"; + } + else + { + var (uvxPath, gitUrl, packageName) = AssetPathUtility.GetUvxCommandParts(); + args = $"mcp add --transport stdio UnityMCP -- \"{uvxPath}\" --from \"{gitUrl}\" {packageName}"; + } - string projectDir = Path.GetDirectoryName(Application.dataPath); + string projectDir = Path.GetDirectoryName(Application.dataPath); + string pathPrepend = BuildPathPrepend(claudePath); - string pathPrepend = null; - if (Application.platform == RuntimePlatform.OSXEditor) - { - pathPrepend = "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"; + bool already = false; + if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend)) + { + string combined = ($"{stdout}\n{stderr}") ?? string.Empty; + if (combined.IndexOf("already exists", StringComparison.OrdinalIgnoreCase) >= 0) + { + already = true; + } + else + { + throw new InvalidOperationException($"Failed to register with Claude Code:\n{stderr}\n{stdout}"); + } + } + + if (!already) + { + McpLog.Info("Successfully registered with Claude Code."); + } + + CheckStatus(); } - else if (Application.platform == RuntimePlatform.LinuxEditor) + finally { - pathPrepend = "/usr/local/bin:/usr/bin:/bin"; + lock (_claudeCliLock) + { + _isClaudeCliRunning = false; + } } + } - try + private void Unregister() + { + lock (_claudeCliLock) { - string claudeDir = Path.GetDirectoryName(claudePath); - if (!string.IsNullOrEmpty(claudeDir)) + if (_isClaudeCliRunning) { - pathPrepend = string.IsNullOrEmpty(pathPrepend) - ? claudeDir - : $"{claudeDir}:{pathPrepend}"; + throw new InvalidOperationException("Claude CLI operation already in progress. Please wait."); } + _isClaudeCliRunning = true; } - catch { } - - bool already = false; - if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend)) + + try { - string combined = ($"{stdout}\n{stderr}") ?? string.Empty; - if (combined.IndexOf("already exists", StringComparison.OrdinalIgnoreCase) >= 0) + var pathService = MCPServiceLocator.Paths; + string claudePath = pathService.GetClaudeCliPath(); + + if (string.IsNullOrEmpty(claudePath)) + { + throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first."); + } + + string projectDir = Path.GetDirectoryName(Application.dataPath); + string pathPrepend = BuildPathPrepend(claudePath); + + bool serverExists = ExecPath.TryRun(claudePath, "mcp get UnityMCP", projectDir, out _, out _, 7000, pathPrepend); + + if (!serverExists) { - already = true; + client.SetStatus(McpStatus.NotConfigured); + McpLog.Info("No MCP for Unity server found - already unregistered."); + return; + } + + if (ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out var stdout, out var stderr, 10000, pathPrepend)) + { + McpLog.Info("MCP server successfully unregistered from Claude Code."); } else { - throw new InvalidOperationException($"Failed to register with Claude Code:\n{stderr}\n{stdout}"); + throw new InvalidOperationException($"Failed to unregister: {stderr}"); } - } - if (!already) + client.SetStatus(McpStatus.NotConfigured); + CheckStatus(); + } + finally { - McpLog.Info("Successfully registered with Claude Code."); + lock (_claudeCliLock) + { + _isClaudeCliRunning = false; + } } - - CheckStatus(); } - private void Unregister() + private static string BuildPathPrepend(string claudePath) { - var pathService = MCPServiceLocator.Paths; - string claudePath = pathService.GetClaudeCliPath(); - - if (string.IsNullOrEmpty(claudePath)) - { - throw new InvalidOperationException("Claude CLI not found. Please install Claude Code first."); - } - - string projectDir = Path.GetDirectoryName(Application.dataPath); string pathPrepend = null; if (Application.platform == RuntimePlatform.OSXEditor) { @@ -493,26 +574,19 @@ private void Unregister() pathPrepend = "/usr/local/bin:/usr/bin:/bin"; } - bool serverExists = ExecPath.TryRun(claudePath, "mcp get UnityMCP", projectDir, out _, out _, 7000, pathPrepend); - - if (!serverExists) - { - client.SetStatus(McpStatus.NotConfigured); - McpLog.Info("No MCP for Unity server found - already unregistered."); - return; - } - - if (ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out var stdout, out var stderr, 10000, pathPrepend)) - { - McpLog.Info("MCP server successfully unregistered from Claude Code."); - } - else + try { - throw new InvalidOperationException($"Failed to unregister: {stderr}"); + string claudeDir = Path.GetDirectoryName(claudePath); + if (!string.IsNullOrEmpty(claudeDir)) + { + pathPrepend = string.IsNullOrEmpty(pathPrepend) + ? claudeDir + : $"{claudeDir}:{pathPrepend}"; + } } + catch { } - client.SetStatus(McpStatus.NotConfigured); - CheckStatus(); + return pathPrepend; } public override string GetManualSnippet() diff --git a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs index d6edcf1aa..e597181db 100644 --- a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs +++ b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs @@ -21,21 +21,15 @@ internal static class EditorPrefKeys internal const string WebSocketUrlOverride = "MCPForUnity.WebSocketUrl"; internal const string GitUrlOverride = "MCPForUnity.GitUrlOverride"; - internal const string PackageDeploySourcePath = "MCPForUnity.PackageDeploy.SourcePath"; - internal const string PackageDeployLastBackupPath = "MCPForUnity.PackageDeploy.LastBackupPath"; - internal const string PackageDeployLastTargetPath = "MCPForUnity.PackageDeploy.LastTargetPath"; - internal const string PackageDeployLastSourcePath = "MCPForUnity.PackageDeploy.LastSourcePath"; - internal const string ServerSrc = "MCPForUnity.ServerSrc"; internal const string UseEmbeddedServer = "MCPForUnity.UseEmbeddedServer"; internal const string LockCursorConfig = "MCPForUnity.LockCursorConfig"; internal const string AutoRegisterEnabled = "MCPForUnity.AutoRegisterEnabled"; - internal const string ToolEnabledPrefix = "MCPForUnity.ToolEnabled."; - internal const string ToolFoldoutStatePrefix = "MCPForUnity.ToolFoldout."; - internal const string EditorWindowActivePanel = "MCPForUnity.EditorWindow.ActivePanel"; internal const string SetupCompleted = "MCPForUnity.SetupCompleted"; internal const string SetupDismissed = "MCPForUnity.SetupDismissed"; + + internal const string EditorWindowActivePanel = "MCPForUnityEditorWindow.ActivePanel"; internal const string CustomToolRegistrationEnabled = "MCPForUnity.CustomToolRegistrationEnabled"; @@ -45,5 +39,18 @@ internal static class EditorPrefKeys internal const string TelemetryDisabled = "MCPForUnity.TelemetryDisabled"; internal const string CustomerUuid = "MCPForUnity.CustomerUUID"; + + // Path Overrides + internal const string PythonPathOverride = "MCPForUnity.PythonPathOverride"; + internal const string NodePathOverride = "MCPForUnity.NodePathOverride"; + internal const string UvPathOverride = "MCPForUnity.UvPathOverride"; + + // Deployment & Tool Config + internal const string PackageDeploySourcePath = "MCPForUnity.Deploy.SourcePath"; + internal const string PackageDeployLastBackupPath = "MCPForUnity.Deploy.LastBackupPath"; + internal const string PackageDeployLastTargetPath = "MCPForUnity.Deploy.LastTargetPath"; + internal const string PackageDeployLastSourcePath = "MCPForUnity.Deploy.LastSourcePath"; + internal const string ToolFoldoutStatePrefix = "MCPForUnity.Tool.Foldout."; + internal const string ToolEnabledPrefix = "MCPForUnity.Tool.Enabled."; } } diff --git a/MCPForUnity/Editor/Data/.keep b/MCPForUnity/Editor/Data/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/MCPForUnity/Editor/Dependencies/DependencyManager.cs b/MCPForUnity/Editor/Dependencies/DependencyManager.cs index 3f7b15455..60cd87cbb 100644 --- a/MCPForUnity/Editor/Dependencies/DependencyManager.cs +++ b/MCPForUnity/Editor/Dependencies/DependencyManager.cs @@ -5,11 +5,24 @@ using MCPForUnity.Editor.Dependencies.Models; using MCPForUnity.Editor.Dependencies.PlatformDetectors; using MCPForUnity.Editor.Helpers; +using MCPForUnity.Editor.Services; using UnityEditor; using UnityEngine; namespace MCPForUnity.Editor.Dependencies { + /// + /// Context object containing all necessary paths and settings for dependency checking. + /// This allows the check to run on a background thread without accessing Unity APIs. + /// + public class DependencyContext + { + public string PackageRootPath { get; set; } + public string PythonOverridePath { get; set; } + public string NodeOverridePath { get; set; } + public string UvOverridePath { get; set; } + } + /// /// Main orchestrator for dependency validation and management /// @@ -41,9 +54,31 @@ public static IPlatformDetector GetCurrentPlatformDetector() } /// - /// Perform a comprehensive dependency check + /// Perform a comprehensive dependency check (Synchronous - Main Thread Only) /// public static DependencyCheckResult CheckAllDependencies() + { + // Gather context on main thread + var context = new DependencyContext + { + PackageRootPath = AssetPathUtility.GetMcpPackageRootPath(), // Changed to match local API + PythonOverridePath = MCPServiceLocator.Paths.GetPythonPath(), + NodeOverridePath = MCPServiceLocator.Paths.GetNodePath(), + UvOverridePath = MCPServiceLocator.Paths.GetUvxPath() + }; + + return CheckAllDependenciesInternal(context); + } + + /// + /// Perform a comprehensive dependency check (Thread-Safe) + /// + public static DependencyCheckResult CheckAllDependenciesAsync(DependencyContext context) + { + return CheckAllDependenciesInternal(context); + } + + private static DependencyCheckResult CheckAllDependenciesInternal(DependencyContext context) { var result = new DependencyCheckResult(); @@ -53,13 +88,52 @@ public static DependencyCheckResult CheckAllDependencies() McpLog.Info($"Checking dependencies on {detector.PlatformName}...", always: false); // Check Python - var pythonStatus = detector.DetectPython(); + var pythonStatus = detector.DetectPython(context.PythonOverridePath); result.Dependencies.Add(pythonStatus); // Check uv - var uvStatus = detector.DetectUv(); + var uvStatus = detector.DetectUv(context.UvOverridePath); result.Dependencies.Add(uvStatus); + // Check Node.js + // Note: If detector doesn't support DetectNode yet, we might need to update detectors too. + // Assuming detectors are being updated or have default interface implementation. + var nodeStatus = detector.DetectNode(context.NodeOverridePath); + result.Dependencies.Add(nodeStatus); + + // Check Server Environment + // We assume ServerEnvironmentSetup.IsEnvironmentReady exists. + // If not, we'll need to update that too. + // Note: IsEnvironmentReady might access Unity API. If so, this "Async" flavor is risky. + // But CheckAllDependencies() is called on main thread in our UI usage. + + // For now, we use a try-catch block specifically for server check to avoid blocking the whole result + try + { + // We use the path gathered on the main thread to ensure thread safety + bool isServerReady = MCPForUnity.Editor.Setup.ServerEnvironmentSetup.IsEnvironmentReady(context.PackageRootPath); + + result.Dependencies.Add(new DependencyStatus("Server Environment", true) + { + Version = isServerReady ? "Installed" : "Missing", + IsAvailable = isServerReady, + Details = isServerReady ? "Virtual Environment Ready" : "Run 'Install Server Environment'", + ErrorMessage = isServerReady ? null : "Virtual environment not set up" + }); + } + catch (Exception ex) + { + McpLog.Warn($"Server environment check failed: {ex.Message}"); + result.Dependencies.Add(new DependencyStatus("Server Environment", true) + { + Version = "Error", + IsAvailable = false, + Details = "Check failed", + ErrorMessage = ex.Message + }); + } + + // Generate summary and recommendations result.GenerateSummary(); GenerateRecommendations(result, detector); @@ -122,12 +196,16 @@ private static void GenerateRecommendations(DependencyCheckResult result, IPlatf { if (dep.Name == "Python") { - result.RecommendedActions.Add($"Install Python 3.10+ from: {detector.GetPythonInstallUrl()}"); + result.RecommendedActions.Add($"Install python 3.11+ from: {detector.GetPythonInstallUrl()}"); } else if (dep.Name == "uv Package Manager") { result.RecommendedActions.Add($"Install uv package manager from: {detector.GetUvInstallUrl()}"); } + else if (dep.Name == "Node.js") + { + result.RecommendedActions.Add("Install Node.js (LTS) from: https://nodejs.org/"); + } else if (dep.Name == "MCP Server") { result.RecommendedActions.Add("MCP Server will be installed automatically when needed."); diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs index 3231105e9..31594995e 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/IPlatformDetector.cs @@ -20,12 +20,17 @@ public interface IPlatformDetector /// /// Detect Python installation on this platform /// - DependencyStatus DetectPython(); + DependencyStatus DetectPython(string overridePath = null); /// /// Detect uv package manager on this platform /// - DependencyStatus DetectUv(); + DependencyStatus DetectUv(string overridePath = null); + + /// + /// Detect Node.js on this platform + /// + DependencyStatus DetectNode(string overridePath = null); /// /// Get platform-specific installation recommendations diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs index 1c5bf4587..b3c16254b 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/LinuxPlatformDetector.cs @@ -16,7 +16,7 @@ public class LinuxPlatformDetector : PlatformDetectorBase public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - public override DependencyStatus DetectPython() + public override DependencyStatus DetectPython(string overridePath = null) { var status = new DependencyStatus("Python", isRequired: true) { @@ -25,6 +25,23 @@ public override DependencyStatus DetectPython() try { + // 1. Check Override + if (overridePath == null) + { + try { overridePath = UnityEditor.EditorPrefs.GetString(MCPForUnity.Editor.Constants.EditorPrefKeys.PythonPathOverride, ""); } catch {} + } + + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + if (TryValidatePython(overridePath, out string ovVersion, out string ovPath)) + { + status.IsAvailable = true; + status.Version = ovVersion; + status.Path = ovPath; + status.Details = $"Using custom Python path: {ovPath}"; + return status; + } + } // Try running python directly first if (TryValidatePython("python3", out string version, out string fullPath) || TryValidatePython("python", out version, out fullPath)) @@ -51,7 +68,7 @@ public override DependencyStatus DetectPython() } status.ErrorMessage = "Python not found in PATH"; - status.Details = "Install Python 3.10+ and ensure it's added to PATH."; + status.Details = "Install python 3.11+ and ensure it's added to PATH."; } catch (Exception ex) { @@ -90,7 +107,7 @@ public override string GetInstallationRecommendations() Note: Make sure ~/.local/bin is in your PATH for user-local installations."; } - public override DependencyStatus DetectUv() + public override DependencyStatus DetectUv(string overridePath = null) { var status = new DependencyStatus("uv Package Manager", isRequired: true) { @@ -99,6 +116,24 @@ public override DependencyStatus DetectUv() try { + // 0. Check Override + if (overridePath == null) + { + try { overridePath = UnityEditor.EditorPrefs.GetString(MCPForUnity.Editor.Constants.EditorPrefKeys.UvPathOverride, ""); } catch {} + } + + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + if (TryValidateUv(overridePath, out string ovVersion, out string ovPath)) + { + status.IsAvailable = true; + status.Version = ovVersion; + status.Path = ovPath; + status.Details = $"Using custom uv path: {ovPath}"; + return status; + } + } + // Try running uv/uvx directly with augmented PATH if (TryValidateUv("uv", out string version, out string fullPath) || TryValidateUv("uvx", out version, out fullPath)) @@ -177,7 +212,7 @@ private bool TryValidatePython(string pythonPath, out string version, out string version = output.Substring(7); // Remove "Python " prefix fullPath = pythonPath; - // Validate minimum version (Python 4+ or Python 3.10+) + // Validate minimum version (Python 4+ or python 3.11+) if (TryParseVersion(version, out var major, out var minor)) { return major > 3 || (major >= 3 && minor >= 10); diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs index 0f9c6e112..59752bbb0 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/MacOSPlatformDetector.cs @@ -16,7 +16,7 @@ public class MacOSPlatformDetector : PlatformDetectorBase public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - public override DependencyStatus DetectPython() + public override DependencyStatus DetectPython(string overridePath = null) { var status = new DependencyStatus("Python", isRequired: true) { @@ -25,33 +25,50 @@ public override DependencyStatus DetectPython() try { - // 1. Try 'which' command with augmented PATH (prioritizing Homebrew) - if (TryFindInPath("python3", out string pathResult) || - TryFindInPath("python", out pathResult)) + // 1. Check Override + if (overridePath == null) + { + try { overridePath = UnityEditor.EditorPrefs.GetString(MCPForUnity.Editor.Constants.EditorPrefKeys.PythonPathOverride, ""); } catch {} + } + + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) { - if (TryValidatePython(pathResult, out string version, out string fullPath)) + if (TryValidatePython(overridePath, out string ovVersion, out string ovPath)) { status.IsAvailable = true; - status.Version = version; - status.Path = fullPath; - status.Details = $"Found Python {version} at {fullPath}"; + status.Version = ovVersion; + status.Path = ovPath; + status.Details = $"Using custom Python path: {ovPath}"; return status; } } - - // 2. Fallback: Try running python directly from PATH - if (TryValidatePython("python3", out string v, out string p) || - TryValidatePython("python", out v, out p)) + // Try running python directly first + if (TryValidatePython("python3", out string version, out string fullPath) || + TryValidatePython("python", out version, out fullPath)) { status.IsAvailable = true; - status.Version = v; - status.Path = p; - status.Details = $"Found Python {v} in PATH"; + status.Version = version; + status.Path = fullPath; + status.Details = $"Found Python {version} in PATH"; return status; } - status.ErrorMessage = "Python not found in PATH or standard locations"; - status.Details = "Install Python 3.10+ via Homebrew ('brew install python3') and ensure it's in your PATH."; + // Fallback: try 'which' command + if (TryFindInPath("python3", out string pathResult) || + TryFindInPath("python", out pathResult)) + { + if (TryValidatePython(pathResult, out version, out fullPath)) + { + status.IsAvailable = true; + status.Version = version; + status.Path = fullPath; + status.Details = $"Found Python {version} in PATH"; + return status; + } + } + + status.ErrorMessage = "Python not found in PATH"; + status.Details = "Install python 3.11+ and ensure it's added to PATH."; } catch (Exception ex) { @@ -88,7 +105,7 @@ public override string GetInstallationRecommendations() Note: If using Homebrew, make sure /opt/homebrew/bin is in your PATH."; } - public override DependencyStatus DetectUv() + public override DependencyStatus DetectUv(string overridePath = null) { var status = new DependencyStatus("uv Package Manager", isRequired: true) { @@ -97,6 +114,23 @@ public override DependencyStatus DetectUv() try { + // 0. Check Override + if (overridePath == null) + { + try { overridePath = UnityEditor.EditorPrefs.GetString(MCPForUnity.Editor.Constants.EditorPrefKeys.UvPathOverride, ""); } catch {} + } + + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + if (TryValidateUv(overridePath, out string ovVersion, out string ovPath)) + { + status.IsAvailable = true; + status.Version = ovVersion; + status.Path = ovPath; + status.Details = $"Using custom uv path: {ovPath}"; + return status; + } + } // Try running uv/uvx directly with augmented PATH if (TryValidateUv("uv", out string version, out string fullPath) || TryValidateUv("uvx", out version, out fullPath)) @@ -166,18 +200,18 @@ private bool TryValidatePython(string pythonPath, out string version, out string using var process = Process.Start(psi); if (process == null) return false; - string output = process.StandardOutput.ReadToEnd().Trim(); process.WaitForExit(5000); + string output = process.StandardOutput.ReadToEnd().Trim(); if (process.ExitCode == 0 && output.StartsWith("Python ")) { version = output.Substring(7); // Remove "Python " prefix fullPath = pythonPath; - // Validate minimum version (Python 4+ or Python 3.10+) + // Validate minimum version (Python 4+ or Python 3.11+) if (TryParseVersion(version, out var major, out var minor)) { - return major > 3 || (major >= 3 && minor >= 10); + return major > 3 || (major == 3 && minor >= 11); } } } @@ -212,8 +246,8 @@ private bool TryValidateUv(string uvPath, out string version, out string fullPat using var process = Process.Start(psi); if (process == null) return false; - string output = process.StandardOutput.ReadToEnd().Trim(); process.WaitForExit(5000); + string output = process.StandardOutput.ReadToEnd().Trim(); if (process.ExitCode == 0 && output.StartsWith("uv ")) { @@ -283,8 +317,8 @@ private bool TryFindInPath(string executable, out string fullPath) using var process = Process.Start(psi); if (process == null) return false; - string output = process.StandardOutput.ReadToEnd().Trim(); process.WaitForExit(3000); + string output = process.StandardOutput.ReadToEnd().Trim(); if (process.ExitCode == 0 && !string.IsNullOrEmpty(output) && File.Exists(output)) { diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/PlatformDetectorBase.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/PlatformDetectorBase.cs index dd554aff4..35bbcdd1e 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/PlatformDetectorBase.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/PlatformDetectorBase.cs @@ -1,6 +1,10 @@ using System; using System.Diagnostics; +using System.IO; +using System.Text; +using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Dependencies.Models; +using UnityEditor; namespace MCPForUnity.Editor.Dependencies.PlatformDetectors { @@ -12,12 +16,12 @@ public abstract class PlatformDetectorBase : IPlatformDetector public abstract string PlatformName { get; } public abstract bool CanDetect { get; } - public abstract DependencyStatus DetectPython(); + public abstract DependencyStatus DetectPython(string overridePath = null); public abstract string GetPythonInstallUrl(); public abstract string GetUvInstallUrl(); public abstract string GetInstallationRecommendations(); - public virtual DependencyStatus DetectUv() + public virtual DependencyStatus DetectUv(string overridePath = null) { var status = new DependencyStatus("uv Package Manager", isRequired: true) { @@ -26,7 +30,26 @@ public virtual DependencyStatus DetectUv() try { - // Try to find uv/uvx in PATH + // 0. Check Override + if (overridePath == null) + { + overridePath = GetEditorPrefsSafely(EditorPrefKeys.UvPathOverride); + } + + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + // Validate version of the override executable + if (TryExecuteProcess(overridePath, "--version", 3000, out string output) && output.StartsWith("uv ")) + { + status.IsAvailable = true; + status.Version = output.Substring(3).Trim(); + status.Path = overridePath; + status.Details = $"Using custom uv path: {overridePath}"; + return status; + } + } + + // 1. Try to find uv/uvx in PATH if (TryFindUvInPath(out string uvPath, out string version)) { status.IsAvailable = true; @@ -36,6 +59,16 @@ public virtual DependencyStatus DetectUv() return status; } + // 2. Fallback: Try to find uv in Python Scripts (if installed via pip but not in PATH) + if (TryFindUvViaPython(out uvPath, out version)) + { + status.IsAvailable = true; + status.Version = version; + status.Path = uvPath; + status.Details = $"Found uv {version} via Python"; + return status; + } + status.ErrorMessage = "uv not found in PATH"; status.Details = "Install uv package manager and ensure it's added to PATH."; } @@ -47,6 +80,112 @@ public virtual DependencyStatus DetectUv() return status; } + protected virtual bool TryFindUvViaPython(out string uvPath, out string version) + { + uvPath = null; + version = null; + try + { + // Ask Python where the Scripts folder is and check for uv + string script = "import sys, os; print(os.path.join(sys.prefix, 'Scripts' if os.name == 'nt' else 'bin', 'uv' + ('.exe' if os.name == 'nt' else '')))"; + + // Assume python is in PATH. + // Note: This might pick up a different python than what the user configured if they have strict overrides, + // but this is a fallback mechanism. + if (TryExecuteProcess("python", $"-c \"{script}\"", 3000, out string path)) + { + if (!string.IsNullOrEmpty(path) && File.Exists(path)) + { + // Found the binary, now check version + if (TryExecuteProcess(path, "--version", 3000, out string output) && output.StartsWith("uv ")) + { + version = output.Substring(3).Trim(); + uvPath = path; + return true; + } + } + } + } + catch (Exception ex) + { + UnityEngine.Debug.LogWarning($"TryFindUvViaPython failed: {ex.Message}"); + } + return false; + } + + public virtual DependencyStatus DetectNode(string overridePath = null) + { + var status = new DependencyStatus("Node.js", isRequired: true) + { + InstallationHint = "https://nodejs.org/" + }; + + try + { + // 1. Check Override + if (overridePath == null) + { + overridePath = GetEditorPrefsSafely(EditorPrefKeys.NodePathOverride); + } + + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + if (TryValidateNode(overridePath, out string version)) + { + status.IsAvailable = true; + status.Version = version; + status.Path = overridePath; + status.Details = $"Using custom Node.js path: {overridePath}"; + return status; + } + } + + // 2. Try to find node in PATH + if (TryFindNodeInPath(out string nodePath, out string nodeVersion)) + { + status.IsAvailable = true; + status.Version = nodeVersion; + status.Path = nodePath; + status.Details = $"Found Node.js {nodeVersion} in PATH"; + return status; + } + + status.ErrorMessage = "Node.js not found in PATH"; + status.Details = "Install Node.js (LTS recommended) and ensure it's added to PATH."; + } + catch (Exception ex) + { + status.ErrorMessage = $"Error detecting Node.js: {ex.Message}"; + } + + return status; + } + + protected bool TryValidateNode(string nodePath, out string version) + { + version = null; + if (TryExecuteProcess(nodePath, "--version", 5000, out string output) && output.StartsWith("v")) + { + version = output.Substring(1).Trim(); + return true; + } + return false; + } + + protected bool TryFindNodeInPath(out string nodePath, out string version) + { + nodePath = null; + version = null; + + if (TryExecuteProcess("node", "--version", 5000, out string output) && output.StartsWith("v")) + { + version = output.Substring(1).Trim(); + nodePath = "node"; + return true; + } + return false; + } + protected bool TryFindUvInPath(out string uvPath, out string version) { uvPath = null; @@ -57,58 +196,104 @@ protected bool TryFindUvInPath(out string uvPath, out string version) foreach (var cmd in commands) { - try + if (TryExecuteProcess(cmd, "--version", 5000, out string output) && output.StartsWith("uv ")) { - var psi = new ProcessStartInfo - { - FileName = cmd, - Arguments = "--version", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; + version = output.Substring(3).Trim(); + uvPath = cmd; + return true; + } + } + return false; + } - using var process = Process.Start(psi); - if (process == null) continue; + // --- Helpers --- + + protected string GetEditorPrefsSafely(string key, string defaultValue = "") + { + try + { + return EditorPrefs.GetString(key, defaultValue); + } + catch (Exception ex) + { + UnityEngine.Debug.LogWarning($"Failed to read EditorPrefs key '{key}': {ex.Message}"); + return defaultValue; + } + } - string output = process.StandardOutput.ReadToEnd().Trim(); - process.WaitForExit(5000); + protected bool TryExecuteProcess(string fileName, string arguments, int timeoutMs, out string output) + { + output = string.Empty; + try + { + var psi = new ProcessStartInfo + { + FileName = fileName, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; - if (process.ExitCode == 0 && output.StartsWith("uv ")) + using var process = new Process { StartInfo = psi, EnableRaisingEvents = true }; + var outputBuilder = new StringBuilder(); + + process.OutputDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) { - version = output.Substring(3).Trim(); - uvPath = cmd; - return true; + outputBuilder.AppendLine(e.Data); } - } - catch + }; + + if (!process.Start()) return false; + + process.BeginOutputReadLine(); + + if (!process.WaitForExit(timeoutMs)) { - // Try next command + try { process.Kill(); } catch { } + return false; } - } - return false; + output = outputBuilder.ToString().Trim(); + return process.ExitCode == 0; + } + catch + { + return false; + } } - - protected bool TryParseVersion(string version, out int major, out int minor) + protected bool TryParseVersion(string versionString, out int major, out int minor) { major = 0; minor = 0; + if (string.IsNullOrEmpty(versionString)) return false; try { - var parts = version.Split('.'); + var parts = versionString.Split('.'); if (parts.Length >= 2) { - return int.TryParse(parts[0], out major) && int.TryParse(parts[1], out minor); + if (int.TryParse(parts[0], out major) && int.TryParse(parts[1], out minor)) + { + return true; + } + } + else if (parts.Length == 1) + { + if (int.TryParse(parts[0], out major)) + { + minor = 0; + return true; + } } } catch { // Ignore parsing errors } - return false; } } diff --git a/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs b/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs index f21d58ff2..ef7a0946c 100644 --- a/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs +++ b/MCPForUnity/Editor/Dependencies/PlatformDetectors/WindowsPlatformDetector.cs @@ -16,7 +16,7 @@ public class WindowsPlatformDetector : PlatformDetectorBase public override bool CanDetect => RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - public override DependencyStatus DetectPython() + public override DependencyStatus DetectPython(string overridePath = null) { var status = new DependencyStatus("Python", isRequired: true) { @@ -25,43 +25,51 @@ public override DependencyStatus DetectPython() try { - // Try running python directly first (works with Windows App Execution Aliases) - if (TryValidatePython("python3.exe", out string version, out string fullPath) || - TryValidatePython("python.exe", out version, out fullPath)) + // 1. Check Override + if (overridePath == null) { - status.IsAvailable = true; - status.Version = version; - status.Path = fullPath; - status.Details = $"Found Python {version} in PATH"; - return status; + try { overridePath = UnityEditor.EditorPrefs.GetString(MCPForUnity.Editor.Constants.EditorPrefKeys.PythonPathOverride, ""); } catch { } } - - // Fallback: try 'where' command - if (TryFindInPath("python3.exe", out string pathResult) || - TryFindInPath("python.exe", out pathResult)) + + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) { - if (TryValidatePython(pathResult, out version, out fullPath)) + if (TryValidatePython(overridePath, out string version, out string fullPath)) { status.IsAvailable = true; status.Version = version; status.Path = fullPath; - status.Details = $"Found Python {version} in PATH"; + status.Details = $"Using custom Python path: {fullPath}"; return status; } } - // Fallback: try to find python via uv - if (TryFindPythonViaUv(out version, out fullPath)) + // 2. Try running python directly first (works with Windows App Execution Aliases) + if (TryValidatePython("python3.exe", out string ver, out string path) || + TryValidatePython("python.exe", out ver, out path)) { status.IsAvailable = true; - status.Version = version; - status.Path = fullPath; - status.Details = $"Found Python {version} via uv"; + status.Version = ver; + status.Path = path; + status.Details = $"Found Python {ver} in PATH"; return status; } + // 3. Fallback: try 'where' command + if (TryFindInPath("python3.exe", out string pathResult) || + TryFindInPath("python.exe", out pathResult)) + { + if (TryValidatePython(pathResult, out ver, out path)) + { + status.IsAvailable = true; + status.Version = ver; + status.Path = path; + status.Details = $"Found Python {ver} in PATH"; + return status; + } + } + status.ErrorMessage = "Python not found in PATH"; - status.Details = "Install Python 3.10+ and ensure it's added to PATH."; + status.Details = "Install python 3.11+ and ensure it's added to PATH."; } catch (Exception ex) { @@ -71,99 +79,103 @@ public override DependencyStatus DetectPython() return status; } - public override string GetPythonInstallUrl() + public override DependencyStatus DetectUv(string overridePath = null) { - return "https://apps.microsoft.com/store/detail/python-313/9NCVDN91XZQP"; - } - - public override string GetUvInstallUrl() - { - return "https://docs.astral.sh/uv/getting-started/installation/#windows"; - } - - public override string GetInstallationRecommendations() - { - return @"Windows Installation Recommendations: - -1. Python: Install from Microsoft Store or python.org - - Microsoft Store: Search for 'Python 3.10' or higher - - Direct download: https://python.org/downloads/windows/ - -2. uv Package Manager: Install via PowerShell - - Run: powershell -ExecutionPolicy ByPass -c ""irm https://astral.sh/uv/install.ps1 | iex"" - - Or download from: https://github.com/astral-sh/uv/releases - -3. MCP Server: Will be installed automatically by MCP for Unity Bridge"; - } - - private bool TryFindPythonViaUv(out string version, out string fullPath) - { - version = null; - fullPath = null; + var status = new DependencyStatus("uv Package Manager", isRequired: true) + { + InstallationHint = GetUvInstallUrl() + }; try { - var psi = new ProcessStartInfo + // 1. Check Override + if (overridePath == null) { - FileName = "uv", // Assume uv is in path or user can't use this fallback - Arguments = "python list", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; + try { overridePath = UnityEditor.EditorPrefs.GetString(MCPForUnity.Editor.Constants.EditorPrefKeys.UvPathOverride, ""); } catch { } + } - using var process = Process.Start(psi); - if (process == null) return false; + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + if (TryValidateUv(overridePath, out string version, out string fullPath)) + { + status.IsAvailable = true; + status.Version = version; + status.Path = fullPath; + status.Details = $"Using custom uv path: {fullPath}"; + return status; + } + } + + // 2. Try running uv directly/PATH + if (TryValidateUv("uv", out string ver, out string path)) + { + status.IsAvailable = true; + status.Version = ver; + status.Path = path; + status.Details = $"Found uv {ver} in PATH"; + return status; + } + + // 3. Try 'where' command + if (TryFindInPath("uv.exe", out string pathResult)) + { + if (TryValidateUv(pathResult, out ver, out path)) + { + status.IsAvailable = true; + status.Version = ver; + status.Path = path; + status.Details = $"Found uv {ver} in PATH"; + return status; + } + } - string output = process.StandardOutput.ReadToEnd(); - process.WaitForExit(5000); + // 4. Force check common install locations + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + + var commonPaths = new[] + { + Path.Combine(localAppData, "Programs", "uv", "uv.exe"), // Standard install + Path.Combine(localAppData, "uv", "uv.exe"), // Alternative + Path.Combine(userProfile, ".cargo", "bin", "uv.exe"), // Cargo install + Path.Combine(localAppData, "bin", "uv.exe") + }; - if (process.ExitCode == 0 && !string.IsNullOrEmpty(output)) + foreach (var candidate in commonPaths) { - var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - foreach (var line in lines) + if (File.Exists(candidate)) { - // Look for installed python paths - // Format is typically: - // Skip lines with "" - if (line.Contains("")) continue; - - // The path is typically the last part of the line - var parts = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length >= 2) + if (TryValidateUv(candidate, out ver, out path)) { - string potentialPath = parts[parts.Length - 1]; - if (File.Exists(potentialPath) && - (potentialPath.EndsWith("python.exe") || potentialPath.EndsWith("python3.exe"))) - { - if (TryValidatePython(potentialPath, out version, out fullPath)) - { - return true; - } - } + status.IsAvailable = true; + status.Version = ver; + status.Path = path; + status.Details = $"Found uv {ver} at {candidate}"; + return status; } } } + + status.ErrorMessage = "uv not found"; + status.Details = "Install uv package manager via PowerShell or from github.com/astral-sh/uv"; } - catch + catch (Exception ex) { - // Ignore errors if uv is not installed or fails + status.ErrorMessage = $"Error detecting uv: {ex.Message}"; } - return false; + return status; } - private bool TryValidatePython(string pythonPath, out string version, out string fullPath) + private bool TryValidateUv(string uvPath, out string version, out string fullPath) { version = null; fullPath = null; - try { var psi = new ProcessStartInfo { - FileName = pythonPath, + FileName = uvPath, Arguments = "--version", UseShellExecute = false, RedirectStandardOutput = true, @@ -174,53 +186,76 @@ private bool TryValidatePython(string pythonPath, out string version, out string using var process = Process.Start(psi); if (process == null) return false; + process.WaitForExit(3000); string output = process.StandardOutput.ReadToEnd().Trim(); - process.WaitForExit(5000); - if (process.ExitCode == 0 && output.StartsWith("Python ")) + if (process.ExitCode == 0 && output.StartsWith("uv")) { - version = output.Substring(7); // Remove "Python " prefix - fullPath = pythonPath; - - // Validate minimum version (Python 4+ or Python 3.10+) - if (TryParseVersion(version, out var major, out var minor)) - { - return major > 3 || (major >= 3 && minor >= 10); - } + version = output.Split(' ')[1]; + fullPath = uvPath; + return true; } } - catch + catch (Exception ex) { - // Ignore validation errors + // Debug logging would help diagnose issues + System.Diagnostics.Debug.WriteLine($"uv validation failed: {ex.Message}"); } - return false; } - private bool TryFindInPath(string executable, out string fullPath) + public override string GetPythonInstallUrl() { - fullPath = null; + return "https://apps.microsoft.com/store/detail/python-313/9NCVDN91XZQP"; + } - try - { - var psi = new ProcessStartInfo - { - FileName = "where", - Arguments = executable, - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; + public override string GetUvInstallUrl() + { + return "https://docs.astral.sh/uv/getting-started/installation/#windows"; + } - using var process = Process.Start(psi); - if (process == null) return false; + public override string GetInstallationRecommendations() + { + return @"Windows Installation Recommendations: - string output = process.StandardOutput.ReadToEnd().Trim(); - process.WaitForExit(3000); +1. Python: Install from Microsoft Store or python.org + - Microsoft Store: Search for 'python 3.11' or higher + - Direct download: https://python.org/downloads/windows/ + +2. uv Package Manager: Install via PowerShell + - Run: powershell -ExecutionPolicy ByPass -c ""irm https://astral.sh/uv/install.ps1 | iex"" + - Or download from: https://github.com/astral-sh/uv/releases + +3. MCP Server: Will be installed automatically by MCP for Unity Bridge"; + } + + private bool TryValidatePython(string pythonPath, out string version, out string fullPath) + { + version = null; + fullPath = null; + + // 5 second timeout for validation + if (TryExecuteProcess(pythonPath, "--version", 5000, out string output) && output.StartsWith("Python ")) + { + version = output.Substring(7); // Remove "Python " prefix + fullPath = pythonPath; - if (process.ExitCode == 0 && !string.IsNullOrEmpty(output)) + // Validate minimum version (Python 4+ or python 3.11+) + if (TryParseVersion(version, out var major, out var minor)) { + return major > 3 || (major == 3 && minor >= 11); + } + } + return false; + } + + private bool TryFindInPath(string executable, out string fullPath) + { + fullPath = null; + + // 3 second timeout for 'where' + if (TryExecuteProcess("where", executable, 3000, out string output) && !string.IsNullOrEmpty(output)) + { // Take the first result var lines = output.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); if (lines.Length > 0) @@ -228,13 +263,7 @@ private bool TryFindInPath(string executable, out string fullPath) fullPath = lines[0].Trim(); return File.Exists(fullPath); } - } - } - catch - { - // Ignore errors } - return false; } } diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs index a310c6e1e..1b337a264 100644 --- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs +++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs @@ -33,6 +33,122 @@ public static string SanitizeAssetPath(string path) return path; } + /// + /// Gets the absolute file system path to the package root. + /// Uses StackTrace/ScriptableObject trick to resolve real path (bypassing PackageCache if symlinked). + /// + public static string GetPackageAbsolutePath() + { + // 1. Try to find path relative to this script file (Most reliable for local dev & mono scripts) + try + { + // Get the path of THIS source file during execution + string scriptFilePath = GetCallerFilePath(); + if (!string.IsNullOrEmpty(scriptFilePath) && File.Exists(scriptFilePath)) + { + // Current file: .../MCPForUnity/Editor/Helpers/AssetPathUtility.cs + // We need: .../MCPForUnity + return Path.GetFullPath(Path.Combine(Path.GetDirectoryName(scriptFilePath), "..", "..")); + } + } + catch (Exception ex) + { + McpLog.Warn($"Failed to resolve path from stack trace: {ex.Message}"); + } + + // 2. Fallback to standard Unity PackageInfo (Reliable for Registry/Git packages) + string packageRoot = GetMcpPackageRootPath(); + if (string.IsNullOrEmpty(packageRoot)) return null; + + // If it's already an absolute path, we're good + if (Path.IsPathRooted(packageRoot)) + { + return packageRoot; + } + + // If it's a virtual path (Packages/...), resolve it to physical path + if (packageRoot.StartsWith("Packages/", StringComparison.OrdinalIgnoreCase)) + { + var packageInfo = PackageInfo.FindForAssembly(typeof(AssetPathUtility).Assembly); + if (packageInfo != null && !string.IsNullOrEmpty(packageInfo.resolvedPath)) + { + return packageInfo.resolvedPath; + } + + // If resolvedPath is failing but we have assetPath, try Path.GetFullPath + // Note: This rarely works for Library/PackageCache, but worth a shot for local tarballs + return Path.GetFullPath(packageRoot); + } + else if (packageRoot.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)) + { + string relativePath = packageRoot.Substring("Assets/".Length); + return Path.Combine(Application.dataPath, relativePath); + } + + return Path.GetFullPath(packageRoot); + } + + private static string GetCallerFilePath([System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "") + { + return sourceFilePath; + } + + /// + /// Gets the absolute path to the wrapper.js file in the package. + /// + /// Absolute path to wrapper.js, or null if not found + public static string GetWrapperJsPath() + { + string packageRoot = GetMcpPackageRootPath(); + if (string.IsNullOrEmpty(packageRoot)) + { + return null; + } + + // wrapper.js is expected to be in {packageRoot}/Server~/wrapper.js + // But we need to handle virtual paths if consistent with GetPackageJson logic + + string wrapperPath; + + // Convert virtual asset path to file system path (similar logic to GetPackageJson) + if (packageRoot.StartsWith("Packages/", StringComparison.OrdinalIgnoreCase)) + { + var packageInfo = PackageInfo.FindForAssembly(typeof(AssetPathUtility).Assembly); + if (packageInfo != null && !string.IsNullOrEmpty(packageInfo.resolvedPath)) + { + wrapperPath = Path.Combine(packageInfo.resolvedPath, "Server~", "wrapper.js"); + } + else + { + return null; + } + } + else if (packageRoot.StartsWith("Assets/", StringComparison.OrdinalIgnoreCase)) + { + string relativePath = packageRoot.Substring("Assets/".Length); + wrapperPath = Path.Combine(Application.dataPath, relativePath, "Server~", "wrapper.js"); + } + else + { + // Already absolute or unknown + wrapperPath = Path.Combine(packageRoot, "Server~", "wrapper.js"); + } + + if (File.Exists(wrapperPath)) + { + return wrapperPath; + } + + // Also check without the ~ just in case + string wrapperPathNoTilde = Path.Combine(Path.GetDirectoryName(wrapperPath), "..", "Server", "wrapper.js"); + if (File.Exists(wrapperPathNoTilde)) + { + return Path.GetFullPath(wrapperPathNoTilde); + } + + return null; + } + /// /// Gets the MCP for Unity package root path. /// Works for registry Package Manager, local Package Manager, and Asset Store installations. @@ -144,22 +260,38 @@ public static JObject GetPackageJson() /// Git URL string, or empty string if version is unknown and no override public static string GetMcpServerGitUrl() { - // Check for Git URL override first + // Check for Git URL override first (still useful for forcing a specific repo) string gitUrlOverride = EditorPrefs.GetString(EditorPrefKeys.GitUrlOverride, ""); if (!string.IsNullOrEmpty(gitUrlOverride)) { return gitUrlOverride; } - // Fall back to default package version - string version = GetPackageVersion(); - if (version == "unknown") + // Return official repository URL as default + // Users can override via EditorPrefs.GitUrlOverride for development/testing + return "git+https://github.com/CoplayDev/unity-mcp.git@main#subdirectory=MCPForUnity/Server~"; + + /* + // Local path logic disabled for clean config aesthetics + string packageRoot = GetPackageAbsolutePath(); + if (!string.IsNullOrEmpty(packageRoot)) { - // Fall back to main repo without pinned version so configs remain valid in test scenarios - return "git+https://github.com/CoplayDev/unity-mcp#subdirectory=Server"; + // The server code is in {packageRoot}/Server~ or {packageRoot}/Server + // Try Server~ first (Unity hidden folder) + string serverPathTilde = Path.Combine(packageRoot, "Server~"); + if (Directory.Exists(serverPathTilde)) + { + return serverPathTilde; + } + + string serverPath = Path.Combine(packageRoot, "Server"); + return serverPath; } - return $"git+https://github.com/CoplayDev/unity-mcp@v{version}#subdirectory=Server"; + // Fallback to git URL if local path cannot be determined + // Note: pip/uv uses '#subdirectory=' syntax, not '?path=' + return "git+https://github.com/CoplayDev/unity-mcp.git@v{version}#subdirectory=MCPForUnity/Server~"; + */ } /// diff --git a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs index 3c0ba7058..32b784424 100644 --- a/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs +++ b/MCPForUnity/Editor/Helpers/ConfigJsonBuilder.cs @@ -78,24 +78,55 @@ private static void PopulateUnityNode(JObject unity, string uvPath, McpClient cl } else { - // Stdio mode: Use uvx command - var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts(); + // Stdio mode: Use node wrapper.js (Recommended for Windows stability) + // This ensures we use the python fallback logic inside wrapper.js to prevent "invalid trailing data" + + string wrapperPath = AssetPathUtility.GetWrapperJsPath(); - var toolArgs = BuildUvxArgs(fromUrl, packageName); - - if (ShouldUseWindowsCmdShim(client)) + if (string.IsNullOrEmpty(wrapperPath)) { - unity["command"] = ResolveCmdPath(); - - var cmdArgs = new List { "/c", uvxPath }; - cmdArgs.AddRange(toolArgs); - - unity["args"] = JArray.FromObject(cmdArgs.ToArray()); + // Fallback to uvx if wrapper not found + var (uvxPath, fromUrl, packageName) = AssetPathUtility.GetUvxCommandParts(); + var toolArgs = BuildUvxArgs(fromUrl, packageName); + + if (ShouldUseWindowsCmdShim(client)) + { + unity["command"] = ResolveCmdPath(); + + var cmdArgs = new List { "/c", uvxPath }; + cmdArgs.AddRange(toolArgs); + + unity["args"] = JArray.FromObject(cmdArgs.ToArray()); + } + else + { + unity["command"] = uvxPath; + unity["args"] = JArray.FromObject(toolArgs.ToArray()); + } } else { - unity["command"] = uvxPath; - unity["args"] = JArray.FromObject(toolArgs.ToArray()); + // Use node to run wrapper.js + // We assume 'node' is in PATH unless overridden. + string nodeCommand = "node"; + string nodeOverride = EditorPrefs.GetString(EditorPrefKeys.NodePathOverride, ""); + if (!string.IsNullOrEmpty(nodeOverride)) + { + if (File.Exists(nodeOverride)) + { + nodeCommand = nodeOverride; + } + else + { + McpLog.Warn($"Node override path not found: {nodeOverride}, falling back to 'node'"); + } + } + + unity["command"] = nodeCommand; + + var args = new List { wrapperPath }; + + unity["args"] = JArray.FromObject(args.ToArray()); } // Remove url/serverUrl if they exist from previous config @@ -114,6 +145,16 @@ private static void PopulateUnityNode(JObject unity, string uvPath, McpClient cl unity.Remove("type"); } + // Force UTF-8 environment variables for Python Stdio stability + // (only meaningful when we're launching a process, i.e., stdio mode) + if (!useHttpTransport) + { + var env = EnsureObject(unity, "env"); + env["PYTHONUTF8"] = "1"; + env["PYTHONIOENCODING"] = "utf-8"; + env["PYTHONUNBUFFERED"] = "1"; + } + bool requiresEnv = client?.EnsureEnvObject == true; bool stripEnv = client?.StripEnvWhenNotRequired == true; diff --git a/MCPForUnity/Editor/Helpers/ExecPath.cs b/MCPForUnity/Editor/Helpers/ExecPath.cs index 2224009ed..966599d30 100644 --- a/MCPForUnity/Editor/Helpers/ExecPath.cs +++ b/MCPForUnity/Editor/Helpers/ExecPath.cs @@ -209,7 +209,37 @@ internal static bool TryRun( if (!process.WaitForExit(timeoutMs)) { - try { process.Kill(); } catch { } + // Timeout occurred - kill the process (child processes may remain due to Unity .NET limitations) + try + { + if (!process.HasExited) + { + int pid = process.Id; + try + { + // Kill process (entireProcessTree not supported in Unity .NET profile) + process.Kill(); + + // Wait a bit to ensure the process actually terminates + if (!process.WaitForExit(1000)) + { + McpLog.Warn($"Process {pid} did not exit after Kill command"); + } + } + catch (InvalidOperationException) + { + // Process already exited - that's fine + } + catch (Exception killEx) + { + McpLog.Warn($"Failed to kill process {pid}: {killEx.Message}"); + } + } + } + catch (Exception ex) + { + McpLog.Debug($"Error during process cleanup: {ex.Message}"); + } return false; } @@ -240,8 +270,8 @@ private static string Which(string exe, string prependPath) string path = Environment.GetEnvironmentVariable("PATH") ?? string.Empty; psi.EnvironmentVariables["PATH"] = string.IsNullOrEmpty(path) ? prependPath : (prependPath + Path.PathSeparator + path); using var p = Process.Start(psi); - string output = p?.StandardOutput.ReadToEnd().Trim(); p?.WaitForExit(1500); + string output = p?.StandardOutput.ReadToEnd().Trim(); return (!string.IsNullOrEmpty(output) && File.Exists(output)) ? output : null; } catch { return null; } @@ -260,10 +290,10 @@ private static string Where(string exe) CreateNoWindow = true, }; using var p = Process.Start(psi); + p?.WaitForExit(1500); string first = p?.StandardOutput.ReadToEnd() .Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries) .FirstOrDefault(); - p?.WaitForExit(1500); return (!string.IsNullOrEmpty(first) && File.Exists(first)) ? first : null; } catch { return null; } diff --git a/MCPForUnity/Editor/MCPForUnity.Editor.asmdef b/MCPForUnity/Editor/MCPForUnity.Editor.asmdef index 47621bdf6..ce06c750d 100644 --- a/MCPForUnity/Editor/MCPForUnity.Editor.asmdef +++ b/MCPForUnity/Editor/MCPForUnity.Editor.asmdef @@ -3,7 +3,9 @@ "rootNamespace": "MCPForUnity.Editor", "references": [ "MCPForUnity.Runtime", - "GUID:560b04d1a97f54a46a2660c3cc343a6f" + "GUID:560b04d1a97f54a46a2660c3cc343a6f", + "UnityEditor.TestRunner", + "UnityEngine.TestRunner" ], "includePlatforms": [ "Editor" diff --git a/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs b/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs index 10f052489..4e256b58d 100644 --- a/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs +++ b/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs @@ -1,29 +1,15 @@ -using MCPForUnity.Editor.Setup; using MCPForUnity.Editor.Windows; using UnityEditor; -using UnityEngine; namespace MCPForUnity.Editor.MenuItems { public static class MCPForUnityMenu { - [MenuItem("Window/MCP For Unity/Setup Window", priority = 1)] - public static void ShowSetupWindow() + [MenuItem("Window/MCP For Unity %#m", priority = 1)] + public static void ShowMCPWindow() { - SetupWindowService.ShowSetupWindow(); - } - - [MenuItem("Window/MCP For Unity/Toggle MCP Window %#m", priority = 2)] - public static void ToggleMCPWindow() - { - if (MCPForUnityEditorWindow.HasAnyOpenWindow()) - { - MCPForUnityEditorWindow.CloseAllOpenWindows(); - } - else - { - MCPForUnityEditorWindow.ShowWindow(); - } + MCPForUnityEditorWindow.ShowWindow(); } } } + diff --git a/MCPForUnity/Editor/Services/ClientConfigurationService.cs b/MCPForUnity/Editor/Services/ClientConfigurationService.cs index 3c48abdf5..e35ab287f 100644 --- a/MCPForUnity/Editor/Services/ClientConfigurationService.cs +++ b/MCPForUnity/Editor/Services/ClientConfigurationService.cs @@ -22,6 +22,8 @@ public ClientConfigurationService() public void ConfigureClient(IMcpClientConfigurator configurator) { + // Ensure status/state is checked before configuring, similar to ConfigureAllDetectedClients + configurator.CheckStatus(attemptAutoRewrite: false); configurator.Configure(); } diff --git a/MCPForUnity/Editor/Services/IPathResolverService.cs b/MCPForUnity/Editor/Services/IPathResolverService.cs index 104c31134..c093c6541 100644 --- a/MCPForUnity/Editor/Services/IPathResolverService.cs +++ b/MCPForUnity/Editor/Services/IPathResolverService.cs @@ -17,6 +17,18 @@ public interface IPathResolverService /// Path to the claude executable, or null if not found string GetClaudeCliPath(); + /// + /// Gets the Python path (respects override if set) + /// + /// Path to the python executable + string GetPythonPath(); + + /// + /// Gets the Node.js path (respects override if set) + /// + /// Path to the node executable + string GetNodePath(); + /// /// Checks if Python is detected on the system /// @@ -51,6 +63,28 @@ public interface IPathResolverService /// void ClearClaudeCliPathOverride(); + /// + /// Sets an override for the Python path + /// + /// Path to override with + void SetPythonPathOverride(string path); + + /// + /// Clears the Python path override + /// + void ClearPythonPathOverride(); + + /// + /// Sets an override for the Node.js path + /// + /// Path to override with + void SetNodePathOverride(string path); + + /// + /// Clears the Node.js path override + /// + void ClearNodePathOverride(); + /// /// Gets whether a uvx path override is active /// @@ -60,5 +94,15 @@ public interface IPathResolverService /// Gets whether a Claude CLI path override is active /// bool HasClaudeCliPathOverride { get; } + + /// + /// Gets whether a Python path override is active + /// + bool HasPythonPathOverride { get; } + + /// + /// Gets whether a Node.js path override is active + /// + bool HasNodePathOverride { get; } } } diff --git a/MCPForUnity/Editor/Services/PathResolverService.cs b/MCPForUnity/Editor/Services/PathResolverService.cs index 4947a16d8..a84e94985 100644 --- a/MCPForUnity/Editor/Services/PathResolverService.cs +++ b/MCPForUnity/Editor/Services/PathResolverService.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Runtime.InteropServices; using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; using UnityEditor; -using UnityEngine; namespace MCPForUnity.Editor.Services { @@ -18,13 +15,15 @@ public class PathResolverService : IPathResolverService { public bool HasUvxPathOverride => !string.IsNullOrEmpty(EditorPrefs.GetString(EditorPrefKeys.UvxPathOverride, null)); public bool HasClaudeCliPathOverride => !string.IsNullOrEmpty(EditorPrefs.GetString(EditorPrefKeys.ClaudeCliPathOverride, null)); + public bool HasPythonPathOverride => !string.IsNullOrEmpty(EditorPrefs.GetString(EditorPrefKeys.PythonPathOverride, null)); + public bool HasNodePathOverride => !string.IsNullOrEmpty(EditorPrefs.GetString(EditorPrefKeys.NodePathOverride, null)); public string GetUvxPath() { try { string overridePath = EditorPrefs.GetString(EditorPrefKeys.UvxPathOverride, string.Empty); - if (!string.IsNullOrEmpty(overridePath)) + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) { return overridePath; } @@ -35,12 +34,6 @@ public string GetUvxPath() McpLog.Debug("No uvx path override found, falling back to default command"); } - string discovered = ResolveUvxFromSystem(); - if (!string.IsNullOrEmpty(discovered)) - { - return discovered; - } - return "uvx"; } @@ -102,109 +95,72 @@ public string GetClaudeCliPath() return null; } - public bool IsPythonDetected() + public string GetPythonPath() { try { - var psi = new ProcessStartInfo + string overridePath = EditorPrefs.GetString(EditorPrefKeys.PythonPathOverride, string.Empty); + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) { - FileName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "python.exe" : "python3", - Arguments = "--version", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - CreateNoWindow = true - }; - using var p = Process.Start(psi); - p.WaitForExit(2000); - return p.ExitCode == 0; + return overridePath; + } } catch { - return false; + McpLog.Debug("No Python path override found, falling back to default command"); } + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "python" : "python3"; } - public bool IsClaudeCliDetected() - { - return !string.IsNullOrEmpty(GetClaudeCliPath()); - } - - private static string ResolveUvxFromSystem() + public string GetNodePath() { try { - foreach (string candidate in EnumerateUvxCandidates()) + string overridePath = EditorPrefs.GetString(EditorPrefKeys.NodePathOverride, string.Empty); + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) { - if (!string.IsNullOrEmpty(candidate) && File.Exists(candidate)) - { - return candidate; - } + return overridePath; } } catch { - // fall back to bare command + McpLog.Debug("No Node path override found, falling back to default command"); } - - return null; + return "node"; } - private static IEnumerable EnumerateUvxCandidates() + public bool IsPythonDetected() { - string exeName = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "uvx.exe" : "uvx"; - - string home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - if (!string.IsNullOrEmpty(home)) - { - yield return Path.Combine(home, ".local", "bin", exeName); - yield return Path.Combine(home, ".cargo", "bin", exeName); - } - - if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - yield return "/opt/homebrew/bin/" + exeName; - yield return "/usr/local/bin/" + exeName; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - yield return "/usr/local/bin/" + exeName; - yield return "/usr/bin/" + exeName; - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + try { - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles); - - if (!string.IsNullOrEmpty(localAppData)) + var psi = new ProcessStartInfo { - yield return Path.Combine(localAppData, "Programs", "uv", exeName); - } - - if (!string.IsNullOrEmpty(programFiles)) + FileName = GetPythonPath(), + Arguments = "--version", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + }; + using var p = Process.Start(psi); + if (!p.WaitForExit(2000)) { - yield return Path.Combine(programFiles, "uv", exeName); + try { p.Kill(); } catch { /* ignore */ } + return false; } + return p.ExitCode == 0; } - - string pathEnv = Environment.GetEnvironmentVariable("PATH"); - if (!string.IsNullOrEmpty(pathEnv)) + catch { - foreach (string rawDir in pathEnv.Split(Path.PathSeparator)) - { - if (string.IsNullOrWhiteSpace(rawDir)) continue; - string dir = rawDir.Trim(); - yield return Path.Combine(dir, exeName); - - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - // Some PATH entries may already contain the file without extension - yield return Path.Combine(dir, "uvx"); - } - } + return false; } } + public bool IsClaudeCliDetected() + { + return !string.IsNullOrEmpty(GetClaudeCliPath()); + } + public void SetUvxPathOverride(string path) { if (string.IsNullOrEmpty(path)) @@ -246,5 +202,49 @@ public void ClearClaudeCliPathOverride() { EditorPrefs.DeleteKey(EditorPrefKeys.ClaudeCliPathOverride); } + + public void SetPythonPathOverride(string path) + { + if (string.IsNullOrEmpty(path)) + { + ClearPythonPathOverride(); + return; + } + + // Allow commands on PATH, but validate explicit paths + if ((Path.IsPathRooted(path) || path.Contains("/") || path.Contains("\\")) && !File.Exists(path)) + { + throw new ArgumentException("The selected Python executable does not exist"); + } + + EditorPrefs.SetString(EditorPrefKeys.PythonPathOverride, path); + } + + public void ClearPythonPathOverride() + { + EditorPrefs.DeleteKey(EditorPrefKeys.PythonPathOverride); + } + + public void SetNodePathOverride(string path) + { + if (string.IsNullOrEmpty(path)) + { + ClearNodePathOverride(); + return; + } + + // Allow commands on PATH, but validate explicit paths + if ((Path.IsPathRooted(path) || path.Contains("/") || path.Contains("\\")) && !File.Exists(path)) + { + throw new ArgumentException("The selected Node executable does not exist"); + } + + EditorPrefs.SetString(EditorPrefKeys.NodePathOverride, path); + } + + public void ClearNodePathOverride() + { + EditorPrefs.DeleteKey(EditorPrefKeys.NodePathOverride); + } } } diff --git a/MCPForUnity/Editor/Services/ServerManagementService.cs b/MCPForUnity/Editor/Services/ServerManagementService.cs index 319275331..169bfb111 100644 --- a/MCPForUnity/Editor/Services/ServerManagementService.cs +++ b/MCPForUnity/Editor/Services/ServerManagementService.cs @@ -13,6 +13,31 @@ namespace MCPForUnity.Editor.Services /// public class ServerManagementService : IServerManagementService { + private static bool _cleanupRegistered = false; + + /// + /// Register cleanup handler for Unity exit + /// + private static void EnsureCleanupRegistered() + { + if (_cleanupRegistered) return; + + EditorApplication.quitting += () => + { + // Try to stop the HTTP server when Unity exits + try + { + var service = new ServerManagementService(); + service.StopLocalHttpServer(); + } + catch (Exception ex) + { + McpLog.Debug($"Cleanup on exit: {ex.Message}"); + } + }; + + _cleanupRegistered = true; + } /// /// Clear the local uvx cache for the MCP server package /// @@ -55,15 +80,17 @@ public bool ClearUvxCache() combinedOutput = "Command failed with no output. Ensure uv is installed, on PATH, or set an override in Advanced Settings."; } - McpLog.Error( + McpLog.Warn( $"Failed to clear uv cache using '{uvCommand} {args}'. " + $"Details: {combinedOutput}{(string.IsNullOrEmpty(lockHint) ? string.Empty : " Hint: " + lockHint)}"); - return false; + + // Cache clearing failure is not critical, so we can return true to proceed + return true; } catch (Exception ex) { - McpLog.Error($"Error clearing uv cache: {ex.Message}"); - return false; + McpLog.Warn($"Error clearing uv cache: {ex.Message}"); + return true; // Proceed anyway } } @@ -75,17 +102,19 @@ private bool ExecuteUvCommand(string uvCommand, string args, out string stdout, string uvxPath = MCPServiceLocator.Paths.GetUvxPath(); string uvPath = BuildUvPathFromUvx(uvxPath); + string extraPathPrepend = GetPlatformSpecificPathPrepend(); + if (!string.Equals(uvCommand, uvPath, StringComparison.OrdinalIgnoreCase)) { - return ExecPath.TryRun(uvCommand, args, Application.dataPath, out stdout, out stderr, 30000); + // Timeout reduced to 3 seconds to prevent UI freezing + return ExecPath.TryRun(uvCommand, args, Application.dataPath, out stdout, out stderr, 3000, extraPathPrepend); } string command = $"{uvPath} {args}"; - string extraPathPrepend = GetPlatformSpecificPathPrepend(); if (Application.platform == RuntimePlatform.WindowsEditor) { - return ExecPath.TryRun("cmd.exe", $"/c {command}", Application.dataPath, out stdout, out stderr, 30000, extraPathPrepend); + return ExecPath.TryRun("cmd.exe", $"/c {command}", Application.dataPath, out stdout, out stderr, 3000, extraPathPrepend); } string shell = File.Exists("/bin/bash") ? "/bin/bash" : "/bin/sh"; @@ -93,10 +122,10 @@ private bool ExecuteUvCommand(string uvCommand, string args, out string stdout, if (!string.IsNullOrEmpty(shell) && File.Exists(shell)) { string escaped = command.Replace("\"", "\\\""); - return ExecPath.TryRun(shell, $"-lc \"{escaped}\"", Application.dataPath, out stdout, out stderr, 30000, extraPathPrepend); + return ExecPath.TryRun(shell, $"-lc \"{escaped}\"", Application.dataPath, out stdout, out stderr, 3000, extraPathPrepend); } - return ExecPath.TryRun(uvPath, args, Application.dataPath, out stdout, out stderr, 30000, extraPathPrepend); + return ExecPath.TryRun(uvPath, args, Application.dataPath, out stdout, out stderr, 3000, extraPathPrepend); } private static string BuildUvPathFromUvx(string uvxPath) @@ -185,13 +214,16 @@ public bool StartLocalHttpServer() "Start Local HTTP Server", $"This will start the MCP server in HTTP mode:\n\n{command}\n\n" + "The server will run in a separate terminal window. " + - "Close the terminal to stop the server.\n\n" + + "Use 'Stop Server' button or close Unity to stop the server.\n\n" + "Continue?", "Start Server", "Cancel")) { try { + // Register cleanup handler for Unity exit + EnsureCleanupRegistered(); + // Start the server in a new terminal window (cross-platform) var startInfo = CreateTerminalProcessStartInfo(command); @@ -372,6 +404,7 @@ public bool TryGetLocalHttpServerCommand(out string command, out string error) ? $"{packageName} --transport http --http-url {httpUrl}" : $"--from {fromUrl} {packageName} --transport http --http-url {httpUrl}"; + // Removed quotes around uvxPath as it caused issues with cmd execution when just "uvx" is returned command = $"{uvxPath} {args}"; return true; } @@ -439,13 +472,25 @@ private System.Diagnostics.ProcessStartInfo CreateTerminalProcessStartInfo(strin // Windows: Use cmd.exe with start command to open new window // Wrap in quotes for /k and escape internal quotes string escapedCommandWin = command.Replace("\"", "\\\""); - return new System.Diagnostics.ProcessStartInfo + var psi = new System.Diagnostics.ProcessStartInfo { FileName = "cmd.exe", + // We need to inject PATH into the new cmd window. + // Since 'start' launches a separate process, we'll try to set PATH before running the command. + // Note: 'start' inherits environment variables, so setting them on this ProcessStartInfo should work. Arguments = $"/c start \"MCP Server\" cmd.exe /k \"{escapedCommandWin}\"", UseShellExecute = false, CreateNoWindow = true }; + + // Inject PATH + string pathPrepend = GetPlatformSpecificPathPrepend(); + if (!string.IsNullOrEmpty(pathPrepend)) + { + string currentPath = Environment.GetEnvironmentVariable("PATH") ?? ""; + psi.EnvironmentVariables["PATH"] = pathPrepend + Path.PathSeparator + currentPath; + } + return psi; #else // Linux: Try common terminal emulators // We use bash -c to execute the command, so we must properly quote/escape for bash @@ -464,7 +509,7 @@ private System.Diagnostics.ProcessStartInfo CreateTerminalProcessStartInfo(strin { try { - var which = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + using var which = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo { FileName = "which", Arguments = term, @@ -472,14 +517,32 @@ private System.Diagnostics.ProcessStartInfo CreateTerminalProcessStartInfo(strin RedirectStandardOutput = true, CreateNoWindow = true }); - which.WaitForExit(5000); // Wait for up to 5 seconds, the command is typically instantaneous - if (which.ExitCode == 0) + + if (which != null) { - terminalCmd = term; - break; + if (!which.WaitForExit(5000)) + { + // Timeout - kill the process + try + { + if (!which.HasExited) + { + which.Kill(); + } + } + catch { } + } + else if (which.ExitCode == 0) + { + terminalCmd = term; + break; + } } } - catch { } + catch (Exception ex) + { + McpLog.Debug($"Terminal check failed for {term}: {ex.Message}"); + } } if (terminalCmd == null) diff --git a/MCPForUnity/Editor/Services/ToolDiscoveryService.cs b/MCPForUnity/Editor/Services/ToolDiscoveryService.cs index 0dcfc79be..354b77baa 100644 --- a/MCPForUnity/Editor/Services/ToolDiscoveryService.cs +++ b/MCPForUnity/Editor/Services/ToolDiscoveryService.cs @@ -131,7 +131,7 @@ private ToolMetadata ExtractToolMetadata(Type type, McpForUnityToolAttribute too ClassName = type.Name, Namespace = type.Namespace ?? "", AssemblyName = type.Assembly.GetName().Name, - AssetPath = ResolveScriptAssetPath(type), + AssetPath = null, // Skip expensive resolution during discovery - resolve lazily if needed AutoRegister = toolAttr.AutoRegister, RequiresPolling = toolAttr.RequiresPolling, PollAction = string.IsNullOrEmpty(toolAttr.PollAction) ? "status" : toolAttr.PollAction @@ -140,11 +140,12 @@ private ToolMetadata ExtractToolMetadata(Type type, McpForUnityToolAttribute too metadata.IsBuiltIn = DetermineIsBuiltIn(type, metadata); if (metadata.IsBuiltIn) { - string summaryDescription = ExtractSummaryDescription(type, metadata); - if (!string.IsNullOrWhiteSpace(summaryDescription)) - { - metadata.Description = summaryDescription; - } + // Skip summary extraction during discovery for performance + // string summaryDescription = ExtractSummaryDescription(type, metadata); + // if (!string.IsNullOrWhiteSpace(summaryDescription)) + // { + // metadata.Description = summaryDescription; + // } } return metadata; diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index 30e31958e..5ad913508 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -43,6 +43,7 @@ public static class StdioBridgeHost private static readonly object startStopLock = new(); private static readonly object clientsLock = new(); private static readonly HashSet activeClients = new(); + private static readonly ConcurrentDictionary activeClientTasks = new(); private static readonly BlockingCollection _outbox = new(new ConcurrentQueue()); private static CancellationTokenSource cts; private static Task listenerTask; @@ -415,6 +416,8 @@ private static TcpListener CreateConfiguredListener(int port) public static void Stop() { Task toWait = null; + Task[] clientTasksToWait = null; + lock (startStopLock) { if (!isRunning) @@ -438,26 +441,45 @@ public static void Stop() } catch (Exception ex) { - McpLog.Error($"Error stopping StdioBridgeHost: {ex.Message}"); + McpLog.Error($"[StdioBridge] Error during stop: {ex.Message}"); } } + // Close all active clients TcpClient[] toClose; lock (clientsLock) { toClose = activeClients.ToArray(); activeClients.Clear(); } + foreach (var c in toClose) { try { c.Close(); } catch { } } + // Collect all active client tasks + clientTasksToWait = activeClientTasks.Values.ToArray(); + + // Wait for listener task if (toWait != null) { try { toWait.Wait(100); } catch { } } + // Wait for all client tasks to complete + if (clientTasksToWait != null && clientTasksToWait.Length > 0) + { + try + { + Task.WaitAll(clientTasksToWait, TimeSpan.FromSeconds(2)); + } + catch (Exception ex) + { + McpLog.Debug($"[StdioBridge] Some client tasks did not complete: {ex.Message}"); + } + } + try { EditorApplication.update -= ProcessCommands; } catch { } try { EditorApplication.quitting -= Stop; } catch { } @@ -498,7 +520,24 @@ private static async Task ListenerLoopAsync(CancellationToken token) client.ReceiveTimeout = 60000; - _ = Task.Run(() => HandleClientAsync(client, token), token); + // Use TaskCompletionSource to ensure the task is tracked in activeClientTasks + // before the handler starts, preventing a race where the handler could complete + // and remove itself before being added to the dictionary. + var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + activeClientTasks.TryAdd(client, tcs.Task); + + _ = Task.Run(async () => + { + try + { + await HandleClientAsync(client, token); + tcs.TrySetResult(null); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } + }, token); } catch (ObjectDisposedException) { @@ -515,7 +554,7 @@ private static async Task ListenerLoopAsync(CancellationToken token) { if (isRunning && !token.IsCancellationRequested) { - if (IsDebugEnabled()) McpLog.Error($"Listener error: {ex.Message}"); + McpLog.Error($"[StdioBridge] Error accepting client: {ex.Message}"); } } } @@ -680,6 +719,7 @@ private static async Task HandleClientAsync(TcpClient client, CancellationToken finally { lock (clientsLock) { activeClients.Remove(client); } + activeClientTasks.TryRemove(client, out _); } } } diff --git a/MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs b/MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs new file mode 100644 index 000000000..b3ca5e9a7 --- /dev/null +++ b/MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs @@ -0,0 +1,327 @@ +using System; +using System.IO; +using System.Diagnostics; +using UnityEditor; +using UnityEngine; +using MCPForUnity.Editor.Helpers; +using MCPForUnity.Editor.Constants; +using System.Threading.Tasks; + +namespace MCPForUnity.Editor.Setup +{ + /// + /// Automates the setup of the Python server environment (venv, dependencies) + /// + public static class ServerEnvironmentSetup + { + public static string ServerRoot => Path.Combine(AssetPathUtility.GetPackageAbsolutePath(), "Server~"); + // Corrected path: .venv is at the root of Server~, not inside Server~/Server + public static string VenvPath => Path.Combine(ServerRoot, ".venv"); + public static string RequirementsPath => Path.Combine(ServerRoot, "Server", "requirements.txt"); + + public static bool IsEnvironmentReady(string packageRootPath = null) + { + string root = packageRootPath ?? AssetPathUtility.GetPackageAbsolutePath(); + if (string.IsNullOrEmpty(root)) + { + McpLog.Warn("[MCP Setup] Package root is null or empty."); + return false; + } + + string serverRoot = Path.Combine(root, "Server~"); + string venvPath = Path.Combine(serverRoot, ".venv"); + + string venvPython = Path.Combine(venvPath, "Scripts", "python.exe"); + if (!File.Exists(venvPython)) + { + venvPython = Path.Combine(venvPath, "bin", "python"); + } + + bool exists = File.Exists(venvPython); + // Debug Log (Remove later) + if (!exists) + { + McpLog.Warn($"[MCP Setup] Python not found at: {venvPython}"); + // Also check if serverRoot exists + if (!Directory.Exists(serverRoot)) McpLog.Warn($"[MCP Setup] Server root directory not found: {serverRoot}"); + } + + return exists; + } + + public static void InstallServerEnvironment() + { + // 0. Pre-check prerequisites (Python & Node) + bool hasPython = CheckPython(); + bool hasNode = CheckNode(); + + if (!hasPython || !hasNode) + { + SetupWindowService.ShowSetupWindow(); + return; + } + + try + { + // 1. Check/Install uv + EditorUtility.DisplayProgressBar("MCP Setup", "Checking 'uv' package manager...", 0.3f); + string uvPath = GetOrInstallUv(); + if (string.IsNullOrEmpty(uvPath)) + { + McpLog.Warn("Could not find 'uv'. Falling back to standard 'pip' for installation (slower)."); + } + + // 3. Create venv (Skip if uv sync is used, as it handles venv creation) + // However, for safety and fallback support, we can still ensure it exists or let uv handle it. + // If using uv sync, we don't necessarily need to manually create venv, but doing so explicitly doesn't hurt. + if (string.IsNullOrEmpty(uvPath)) + { + EditorUtility.DisplayProgressBar("MCP Setup", "Creating virtual environment...", 0.5f); + if (!CreateVenv()) + { + EditorUtility.ClearProgressBar(); + EditorUtility.DisplayDialog("Error", "Failed to create virtual environment.", "OK"); + return; + } + } + + // 4. Install Dependencies + EditorUtility.DisplayProgressBar("MCP Setup", "Syncing dependencies...", 0.7f); + if (!InstallDependencies(uvPath)) + { + EditorUtility.ClearProgressBar(); + EditorUtility.DisplayDialog("Error", "Failed to install dependencies.", "OK"); + return; + } + + EditorUtility.ClearProgressBar(); + EditorUtility.DisplayDialog("Success", "MCP Server environment setup complete!\n\nYou can now connect using Cursor/Claude.", "OK"); + } + catch (Exception ex) + { + EditorUtility.ClearProgressBar(); + McpLog.Error($"[MCP Setup] Error: {ex}"); + EditorUtility.DisplayDialog("Setup Failed", + $"Setup failed: {ex.Message}\n\nRunning processes might be locking files, or PATH environment variables might need a refresh.\n\nTry restarting Unity (or your computer) and run Setup again.", "OK"); + } + } + + private static bool CheckPython() + { + string pythonCmd = GetPythonCommand(); + if (RunCommand(pythonCmd, "--version", out string output)) + { + output = output.Trim(); + // Output format: "Python 3.x.x" + if (output.StartsWith("Python ")) + { + string versionStr = output.Substring(7); + string[] parts = versionStr.Split('.'); + if (parts.Length >= 2 && + int.TryParse(parts[0], out int major) && + int.TryParse(parts[1], out int minor)) + { + if (major > 3 || (major == 3 && minor >= 11)) return true; + else + { + McpLog.Error($"[MCP Setup] Python version {versionStr} is too old. Required: 3.11+"); + return false; + } + } + } + McpLog.Warn($"[MCP Setup] Could not parse Python version: {output}"); + return false; + } + return false; + } + + private static bool CheckNode() + { + // Use override if available + return RunCommand(GetNodeCommand(), "--version", out _); + } + + public static string InstallUvExplicitly() + { + return GetOrInstallUv(); + } + + private static string GetOrInstallUv() + { + // Check if uv is already in PATH + if (RunCommand("uv", "--version", out _)) return "uv"; + + // Check if uv is in the known install locations (override or default) + string overridePath = EditorPrefs.GetString(EditorPrefKeys.UvPathOverride, ""); + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) return overridePath; + + McpLog.Info("[MCP Setup] 'uv' not found in PATH. Installing via pip..."); + + string pythonCmd = GetPythonCommand(); + + // Install via pip + if (!RunCommand(pythonCmd, "-m pip install uv", out string pipOutput)) + { + McpLog.Warn($"Failed to install uv via pip: {pipOutput}"); + return null; + } + + // Check if uv is now in PATH + if (RunCommand("uv", "--version", out _)) return "uv"; + + // Try to find uv in likely locations (Fallback logic) + string findUvScript = "import site, os, sys; " + + "candidates = [" + + "os.path.join(sys.prefix, 'Scripts', 'uv.exe'), " + + "os.path.join(sys.prefix, 'bin', 'uv'), " + + "os.path.join(site.getuserbase(), 'Scripts', 'uv.exe'), " + + "os.path.join(site.getuserbase(), 'bin', 'uv')" + + "]; " + + "found = next((p for p in candidates if os.path.exists(p)), ''); " + + "print(found)"; + + if (RunCommand(pythonCmd, $"-c \"{findUvScript}\"", out string foundPath)) + { + foundPath = foundPath.Trim(); + if (!string.IsNullOrEmpty(foundPath) && File.Exists(foundPath)) return foundPath; + } + + return null; + } + + /// + /// Creates a Python virtual environment manually. No-op if UV is available (UV handles venv creation). + /// + private static bool CreateVenv() + { + if (Directory.Exists(VenvPath)) + { + McpLog.Info("[MCP Setup] Cleaning existing .venv..."); + try { Directory.Delete(VenvPath, true); } catch { /* ignore */ } + } + + McpLog.Info($"[MCP Setup] Creating virtual environment at: {VenvPath}"); + return RunCommand(GetPythonCommand(), $"-m venv \"{VenvPath}\"", out string output, workingDirectory: ServerRoot); + } + + private static bool InstallDependencies(string uvPath) + { + string workingDir = Path.GetFullPath(ServerRoot); + + if (!string.IsNullOrEmpty(uvPath)) + { + McpLog.Info("[MCP Setup] Using 'uv sync' to install dependencies..."); + // uv sync will create .venv if needed and install everything defined in pyproject.toml + // Removed --frozen to allow lock file generation if missing + return RunCommand(uvPath, "sync", out string output, workingDirectory: workingDir); + } + else + { + // Fallback for standard pip + string venvPython = Path.Combine(VenvPath, "Scripts", "python.exe"); + if (!File.Exists(venvPython)) venvPython = Path.Combine(VenvPath, "bin", "python"); + venvPython = Path.GetFullPath(venvPython); + + if (!File.Exists(venvPython)) + { + McpLog.Error($"[MCP Setup] Virtual environment python not found at: {venvPython}"); + return false; + } + + McpLog.Info("[MCP Setup] Using standard pip to install dependencies..."); + return RunCommand(venvPython, "-m pip install -e .", out string output, workingDirectory: workingDir); + } + } + + private static bool RunCommand(string fileName, string arguments, out string output, string workingDirectory = null) + { + output = ""; + try + { + ProcessStartInfo psi = new ProcessStartInfo(); + psi.FileName = fileName; + psi.Arguments = arguments; + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + psi.CreateNoWindow = true; + + if (!string.IsNullOrEmpty(workingDirectory)) + { + psi.WorkingDirectory = workingDirectory; + } + + using (Process p = Process.Start(psi)) + { + if (p == null) + { + McpLog.Error($"[MCP Setup] Failed to start process: {fileName}"); + return false; + } + + // Read streams asynchronously to prevent deadlock on buffer fill + // Note: ReadToEndAsync is not available in .NET Standard 2.0 (Unity's default profile), using wrapper or async delegate + var outputTask = Task.Run(() => p.StandardOutput.ReadToEnd()); + var errorTask = Task.Run(() => p.StandardError.ReadToEnd()); + + // 2 minute timeout to prevent hanging indefinitely + if (!p.WaitForExit(120000)) + { + try { p.Kill(); } catch {} + McpLog.Error($"[MCP Setup] Command timed out: {fileName} {arguments}"); + return false; + } + + output = outputTask.Result; + string error = errorTask.Result; + + // 2 minute timeout to prevent hanging indefinitely + if (!p.WaitForExit(120000)) + { + try { p.Kill(); } catch {} + McpLog.Error($"[MCP Setup] Command timed out: {fileName} {arguments}"); + return false; + } + + if (p.ExitCode != 0) + { + McpLog.Error($"[MCP Setup] Command failed: {fileName} {arguments}\nOutput: {output}\nError: {error}"); + return false; + } + return true; + } + } + catch (System.ComponentModel.Win32Exception) + { + // File not found (common when checking if a tool exists in PATH). + // Do not log as Error to avoid confusing the user. + return false; + } + catch (Exception ex) + { + McpLog.Error($"[MCP Setup] Exception running command '{fileName} {arguments}': {ex.Message}"); + return false; + } + } + + private static string GetPythonCommand() + { + string overridePath = EditorPrefs.GetString(EditorPrefKeys.PythonPathOverride, ""); + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + return overridePath; + } + return "python"; + } + + private static string GetNodeCommand() + { + string overridePath = EditorPrefs.GetString(EditorPrefKeys.NodePathOverride, ""); + if (!string.IsNullOrEmpty(overridePath) && File.Exists(overridePath)) + { + return overridePath; + } + return "node"; + } + } +} diff --git a/MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs.meta b/MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs.meta new file mode 100644 index 000000000..1ac299141 --- /dev/null +++ b/MCPForUnity/Editor/Setup/ServerEnvironmentSetup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a1b2c3d4e5f607182930415263748596 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 781d1c0ed..8e7e733f4 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -41,7 +41,7 @@ public class McpClientConfigSection private readonly List configurators; private readonly Dictionary lastStatusChecks = new(); private readonly HashSet statusRefreshInFlight = new(); - private static readonly TimeSpan StatusRefreshInterval = TimeSpan.FromSeconds(45); + private int selectedClientIndex = 0; public VisualElement Root { get; private set; } @@ -90,9 +90,10 @@ private void RegisterCallbacks() clientDropdown.RegisterValueChangedCallback(evt => { selectedClientIndex = clientDropdown.index; - UpdateClientStatus(); + // Only update UI, don't check status automatically UpdateManualConfiguration(); UpdateClaudeCliPathVisibility(); + ApplyStatusToUi(configurators[selectedClientIndex]); }); configureAllButton.clicked += OnConfigureAllClientsClicked; @@ -103,14 +104,7 @@ private void RegisterCallbacks() copyJsonButton.clicked += OnCopyJsonClicked; } - public void UpdateClientStatus() - { - if (selectedClientIndex < 0 || selectedClientIndex >= configurators.Count) - return; - var client = configurators[selectedClientIndex]; - RefreshClientStatus(client); - } private string GetStatusDisplayString(McpStatus status) { @@ -198,8 +192,9 @@ private void OnConfigureAllClientsClicked() if (selectedClientIndex >= 0 && selectedClientIndex < configurators.Count) { - UpdateClientStatus(); + // Just update UI, don't check status UpdateManualConfiguration(); + ApplyStatusToUi(configurators[selectedClientIndex]); } } catch (Exception ex) @@ -243,7 +238,7 @@ private void OnBrowseClaudeClicked() { MCPServiceLocator.Paths.SetClaudeCliPathOverride(picked); UpdateClaudeCliPathVisibility(); - UpdateClientStatus(); + // Status will be checked when user clicks Configure button McpLog.Info($"Claude CLI path override set to: {picked}"); } catch (Exception ex) @@ -307,9 +302,16 @@ private void RefreshClientStatus(IMcpClientConfigurator client, bool forceImmedi return; } - if (forceImmediate || ShouldRefreshClient(client)) + // Only check status when explicitly requested (forceImmediate = true) + // This happens when: + // - User clicks Configure button + // - User changes client dropdown + // - User manually refreshes + if (forceImmediate) { - MCPServiceLocator.Client.CheckClientStatus(client); + // Only check status, don't auto-rewrite config files + // Config files should only be written when user explicitly clicks Configure + MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false); lastStatusChecks[client] = DateTime.UtcNow; } @@ -318,72 +320,17 @@ private void RefreshClientStatus(IMcpClientConfigurator client, bool forceImmedi private void RefreshClaudeCliStatus(IMcpClientConfigurator client, bool forceImmediate) { + // For Claude CLI, only check status when explicitly requested (forceImmediate) if (forceImmediate) { MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false); lastStatusChecks[client] = DateTime.UtcNow; - ApplyStatusToUi(client); - return; } - bool hasStatus = lastStatusChecks.ContainsKey(client); - bool needsRefresh = !hasStatus || ShouldRefreshClient(client); - - if (!hasStatus) - { - ApplyStatusToUi(client, showChecking: true); - } - else - { - ApplyStatusToUi(client); - } - - if (needsRefresh && !statusRefreshInFlight.Contains(client)) - { - statusRefreshInFlight.Add(client); - ApplyStatusToUi(client, showChecking: true); - - Task.Run(() => - { - MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false); - }).ContinueWith(t => - { - bool faulted = false; - string errorMessage = null; - if (t.IsFaulted && t.Exception != null) - { - var baseException = t.Exception.GetBaseException(); - errorMessage = baseException?.Message ?? "Status check failed"; - McpLog.Error($"Failed to refresh Claude CLI status: {errorMessage}"); - faulted = true; - } - - EditorApplication.delayCall += () => - { - statusRefreshInFlight.Remove(client); - lastStatusChecks[client] = DateTime.UtcNow; - if (faulted) - { - if (client is McpClientConfiguratorBase baseConfigurator) - { - baseConfigurator.Client.SetStatus(McpStatus.Error, errorMessage ?? "Status check failed"); - } - } - ApplyStatusToUi(client); - }; - }); - } + ApplyStatusToUi(client); } - private bool ShouldRefreshClient(IMcpClientConfigurator client) - { - if (!lastStatusChecks.TryGetValue(client, out var last)) - { - return true; - } - return (DateTime.UtcNow - last) > StatusRefreshInterval; - } private void ApplyStatusToUi(IMcpClientConfigurator client, bool showChecking = false) { diff --git a/MCPForUnity/Editor/Windows/Components/Common.uss b/MCPForUnity/Editor/Windows/Components/Common.uss index e89e0bec7..87e62ee6a 100644 --- a/MCPForUnity/Editor/Windows/Components/Common.uss +++ b/MCPForUnity/Editor/Windows/Components/Common.uss @@ -359,7 +359,12 @@ } .tool-parameters { - font-style: italic; + -unity-font-style: italic; +} + +.tool-item-actions { + flex-direction: row; + margin-top: 6px; } /* Advanced Settings */ @@ -469,22 +474,6 @@ border-color: rgba(0, 0, 0, 0.15); } -.unity-theme-dark .tool-tag { - color: rgba(220, 220, 220, 1); - background-color: rgba(80, 80, 80, 0.6); -} - -.unity-theme-dark .tool-item { - background-color: rgba(255, 255, 255, 0.04); - border-color: rgba(255, 255, 255, 0.08); -} - -.unity-theme-dark .tool-item-description, -.unity-theme-dark .tool-parameters { - color: rgba(200, 200, 200, 0.8); -} - - .unity-theme-light .validation-description { background-color: rgba(100, 150, 200, 0.1); } @@ -520,3 +509,144 @@ .unity-theme-light .path-display-field > .unity-text-field__input { background-color: rgba(0, 0, 0, 0.05); } + +/* Dependency Row Styles */ +.dependency-row { + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 4px 0; + height: 30px; +} + +.dependency-info { + flex-direction: row; + align-items: center; + flex-grow: 1; +} + +.dependency-label { + width: 120px; + -unity-font-style: bold; +} + +.unity-theme-light .dependency-label { + color: #333333; +} + +.unity-theme-dark .dependency-label { + color: #E0E0E0; +} + +.dependency-status { + margin-left: 10px; +} + +.unity-theme-dark .dependency-status { + color: #AAAAAA; +} + +.unity-theme-dark .dependency-status.installed { + color: #4CAF50; /* Green */ +} + +.unity-theme-dark .dependency-status.missing { + color: #F44336; /* Red */ +} + +.unity-theme-light .dependency-status { + color: #666666; +} + +.unity-theme-light .dependency-status.installed { + color: #2E7D32; +} + +.unity-theme-light .dependency-status.missing { + color: #C62828; +} + +.dependency-actions { + flex-direction: row; + align-items: center; +} + +.mini-button { + height: 24px; + padding: 0 10px; + margin-left: 5px; + font-size: 11px; + border-width: 0; + border-radius: 3px; +} + +.unity-theme-dark .mini-button { + background-color: #444; + color: #EEE; +} + +.unity-theme-dark .mini-button:hover { + background-color: #555; +} + +.unity-theme-dark .mini-button.secondary { + background-color: transparent; + border-width: 1px; + border-color: #555; +} + +.unity-theme-dark .mini-button.secondary:hover { + background-color: #383838; +} + +.unity-theme-dark .mini-button.primary { + background-color: #2196F3; /* Blue */ +} + +.unity-theme-dark .mini-button.primary:hover { + background-color: #1E88E5; +} + +.unity-theme-light .mini-button { + background-color: #E0E0E0; + color: #333; +} + +.unity-theme-light .mini-button:hover { + background-color: #CCCCCC; +} + +.unity-theme-light .mini-button.secondary { + background-color: transparent; + border-width: 1px; + border-color: #AAAAAA; +} + +.unity-theme-light .mini-button.secondary:hover { + background-color: #F5F5F5; +} + +.unity-theme-light .mini-button.primary { + background-color: #2196F3; + color: #FFF; +} + +.unity-theme-light .mini-button.primary:hover { + background-color: #1976D2; +} + +/* Dark Theme Tool Overrides */ +.unity-theme-dark .tool-tag { + color: rgba(220, 220, 220, 1); + background-color: rgba(80, 80, 80, 0.6); +} + +.unity-theme-dark .tool-item { + background-color: rgba(255, 255, 255, 0.04); + border-color: rgba(255, 255, 255, 0.08); +} + +.unity-theme-dark .tool-item-description, +.unity-theme-dark .tool-parameters { + color: rgba(200, 200, 200, 0.8); +} diff --git a/MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs b/MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs index 1489304e9..48327497d 100644 --- a/MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Settings/McpSettingsSection.cs @@ -13,7 +13,8 @@ namespace MCPForUnity.Editor.Windows.Components.Settings { /// /// Controller for the Settings section of the MCP For Unity editor window. - /// Handles version display, debug logs, validation level, and advanced path overrides. + /// Handles version display, debug logs, validation level. + /// Advanced settings and overrides are handled in McpSetupSection or McpClientConfigSection. /// public class McpSettingsSection { @@ -22,28 +23,12 @@ public class McpSettingsSection private Toggle debugLogsToggle; private EnumField validationLevelField; private Label validationDescription; - private Foldout advancedSettingsFoldout; - private TextField uvxPathOverride; - private Button browseUvxButton; - private Button clearUvxButton; - private VisualElement uvxPathStatus; - private TextField gitUrlOverride; - private Button clearGitUrlButton; - private TextField deploySourcePath; - private Button browseDeploySourceButton; - private Button clearDeploySourceButton; - private Button deployButton; - private Button deployRestoreButton; - private Label deployTargetLabel; - private Label deployBackupLabel; - private Label deployStatusLabel; // Data private ValidationLevel currentValidationLevel = ValidationLevel.Standard; // Events - public event Action OnGitUrlChanged; - public event Action OnHttpServerCommandUpdateRequested; + // Validation levels private enum ValidationLevel @@ -70,21 +55,6 @@ private void CacheUIElements() debugLogsToggle = Root.Q("debug-logs-toggle"); validationLevelField = Root.Q("validation-level"); validationDescription = Root.Q