├── .python-version ├── tests ├── __init__.py ├── conftest.py ├── test_icon_paths.py ├── test_package.py ├── test_mcp_server.py ├── test_entry_points.py └── test_readme_features.py ├── assets └── cursor-icon.icns ├── .gitignore ├── src └── ask_human_for_context_mcp │ ├── __main__.py │ ├── __init__.py │ └── server.py ├── mcp-server-config.json ├── LICENSE ├── pyproject.toml ├── README.md └── uv.lock /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """Tests for ask-human-for-context-mcp package.""" 2 | -------------------------------------------------------------------------------- /assets/cursor-icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/galprz/ask-human-for-context/HEAD/assets/cursor-icon.icns -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python-generated files 2 | __pycache__/ 3 | *.py[oc] 4 | build/ 5 | dist/ 6 | wheels/ 7 | *.egg-info 8 | scratchpad/ 9 | # Virtual environments 10 | .venv 11 | .history/ -------------------------------------------------------------------------------- /src/ask_human_for_context_mcp/__main__.py: -------------------------------------------------------------------------------- 1 | """Entry point for running the Ask Human for Context MCP server as a module.""" 2 | 3 | from .server import main 4 | 5 | if __name__ == "__main__": 6 | main() -------------------------------------------------------------------------------- /src/ask_human_for_context_mcp/__init__.py: -------------------------------------------------------------------------------- 1 | """Ask Human for Context MCP Server - GUI dialogs for AI assistant interaction.""" 2 | 3 | __version__ = "1.0.3" 4 | 5 | from .server import main 6 | 7 | __all__ = ["main"] -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Test configuration for pytest.""" 2 | import os 3 | import sys 4 | 5 | # Add src directory to Python path for testing 6 | test_dir = os.path.dirname(__file__) 7 | src_dir = os.path.join(test_dir, "..", "src") 8 | sys.path.insert(0, os.path.abspath(src_dir)) 9 | -------------------------------------------------------------------------------- /mcp-server-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "ask-human-for-context-local": { 3 | "command": "python", 4 | "args": ["server.py", "--transport", "stdio"], 5 | "cwd": "/Users/galperetz/custom-mcp-servers/mcp-server-python-template", 6 | "env": { 7 | "PYTHONPATH": "/Users/galperetz/custom-mcp-servers/mcp-server-python-template" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tests/test_icon_paths.py: -------------------------------------------------------------------------------- 1 | """Test icon path resolution functionality.""" 2 | import pytest 3 | import sys 4 | import os 5 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) 6 | 7 | def test_icon_path_absolute(): 8 | """Test that icon paths are resolved as absolute paths.""" 9 | from ask_human_for_context_mcp.server import GUIDialogHandler 10 | handler = GUIDialogHandler() 11 | # This should not raise an exception 12 | assert handler is not None 13 | -------------------------------------------------------------------------------- /tests/test_package.py: -------------------------------------------------------------------------------- 1 | # Test basic package functionality 2 | import pytest 3 | import sys, os 4 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src')) 5 | def test_package_import(): 6 | """Test that the package can be imported successfully.""" 7 | import ask_human_for_context_mcp 8 | assert ask_human_for_context_mcp is not None 9 | 10 | def test_package_version(): 11 | """Test that the package version is correct.""" 12 | import ask_human_for_context_mcp 13 | assert ask_human_for_context_mcp.__version__ == "1.0.3" 14 | -------------------------------------------------------------------------------- /tests/test_mcp_server.py: -------------------------------------------------------------------------------- 1 | """Test MCP server functionality.""" 2 | import pytest 3 | import sys 4 | import os 5 | import asyncio 6 | import json 7 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) 8 | 9 | def test_mcp_server_exists(): 10 | """Test that MCP server can be initialized.""" 11 | from ask_human_for_context_mcp.server import mcp 12 | assert mcp is not None 13 | 14 | def test_asking_user_missing_context_tool(): 15 | """Test that the main tool function exists.""" 16 | from ask_human_for_context_mcp.server import asking_user_missing_context 17 | assert callable(asking_user_missing_context) 18 | -------------------------------------------------------------------------------- /tests/test_entry_points.py: -------------------------------------------------------------------------------- 1 | """Test entry points and command line functionality.""" 2 | import pytest 3 | import subprocess 4 | import sys 5 | import os 6 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) 7 | 8 | def test_main_module_execution(): 9 | """Test that the package can be run as a module.""" 10 | result = subprocess.run([sys.executable, "-m", "ask_human_for_context_mcp", "--help"], capture_output=True, text=True, cwd=os.path.join(os.path.dirname(__file__), "..")) 11 | assert result.returncode == 0 12 | assert "transport" in result.stdout 13 | 14 | def test_installed_command(): 15 | """Test that the installed command works.""" 16 | try: 17 | result = subprocess.run(["ask-human-for-context-mcp", "--help"], capture_output=True, text=True) 18 | assert result.returncode == 0 19 | assert "transport" in result.stdout 20 | except FileNotFoundError: 21 | pytest.skip("ask-human-for-context-mcp command not installed") 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Sontal/Sun Hui 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /tests/test_readme_features.py: -------------------------------------------------------------------------------- 1 | """Test functionality mentioned in README.""" 2 | import pytest 3 | import sys 4 | import os 5 | import platform 6 | sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) 7 | 8 | def test_platform_detection(): 9 | """Test that platform detection works as mentioned in README.""" 10 | from ask_human_for_context_mcp.server import GUIDialogHandler 11 | handler = GUIDialogHandler() 12 | assert handler.platform in ["Darwin", "Linux", "Windows"] 13 | # On macOS (Darwin), should be detected correctly 14 | if platform.system() == "Darwin": 15 | assert handler.platform == "Darwin" 16 | 17 | def test_error_handling_classes(): 18 | """Test custom exception classes mentioned in README.""" 19 | from ask_human_for_context_mcp.server import ( 20 | UserPromptTimeout, 21 | UserPromptCancelled, 22 | UserPromptError 23 | ) 24 | # Test that exceptions can be instantiated 25 | timeout_err = UserPromptTimeout("Test timeout") 26 | cancelled_err = UserPromptCancelled("Test cancellation") 27 | prompt_err = UserPromptError("Test error") 28 | assert str(timeout_err) == "Test timeout" 29 | assert str(cancelled_err) == "Test cancellation" 30 | assert str(prompt_err) == "Test error" 31 | 32 | def test_tool_parameter_validation(): 33 | """Test tool parameter validation as described in README.""" 34 | from ask_human_for_context_mcp.server import asking_user_missing_context 35 | import asyncio 36 | # This is an async function, so we need to test it properly 37 | assert asyncio.iscoroutinefunction(asking_user_missing_context) 38 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "ask-human-for-context-mcp" 7 | version = "1.0.3" 8 | description = "Ask Human for Context MCP Server - GUI dialogs for AI assistant interaction" 9 | authors = [{name = "Gal Peretz", email = "gal@example.com"}] 10 | license = {text = "MIT"} 11 | readme = "README.md" 12 | requires-python = ">=3.10" 13 | keywords = ["mcp", "ask-human-for-context", "gui", "dialog", "cursor", "ai-assistant"] 14 | classifiers = [ 15 | "Development Status :: 5 - Production/Stable", 16 | "Intended Audience :: Developers", 17 | "License :: OSI Approved :: MIT License", 18 | "Programming Language :: Python :: 3", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Topic :: Software Development :: Libraries :: Python Modules", 23 | "Topic :: Communications :: Chat", 24 | "Topic :: Desktop Environment :: File Managers" 25 | ] 26 | 27 | dependencies = [ 28 | "mcp", 29 | "starlette", 30 | "uvicorn", 31 | ] 32 | 33 | [project.optional-dependencies] 34 | dev = [ 35 | "pytest>=7.0.0", 36 | "black>=22.0.0", 37 | "isort>=5.0.0", 38 | "mypy>=1.0.0" 39 | ] 40 | 41 | [project.urls] 42 | Homepage = "https://github.com/galperetz/ask-human-for-context-mcp" 43 | Repository = "https://github.com/galperetz/ask-human-for-context-mcp" 44 | Issues = "https://github.com/galperetz/ask-human-for-context-mcp/issues" 45 | 46 | [project.scripts] 47 | ask-human-for-context-mcp = "ask_human_for_context_mcp:main" 48 | 49 | 50 | 51 | [tool.black] 52 | line-length = 100 53 | target-version = ['py310'] 54 | 55 | [tool.isort] 56 | profile = "black" 57 | line_length = 100 58 | 59 | [tool.mypy] 60 | python_version = "3.10" 61 | warn_return_any = true 62 | warn_unused_configs = true 63 | disallow_untyped_defs = true 64 | 65 | [dependency-groups] 66 | dev = [ 67 | "pytest>=8.3.5", 68 | ] 69 | 70 | [tool.pytest.ini_options] 71 | testpaths = ["tests"] 72 | python_files = ["test_*.py"] 73 | python_functions = ["test_*"] 74 | addopts = ["-v", "--tb=short"] 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ask Human for Context MCP Server 2 | 3 | > **Bridge the gap between AI and human intelligence** - A Model Context Protocol (MCP) server that enables AI assistants to ask humans for missing context during conversations and development workflows. 4 | 5 | [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/) 6 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 7 | [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io/) 8 | 9 | ## 🤖 What is this? 10 | 11 | The **Ask Human for Context MCP Server** is a specialized tool that allows AI assistants (like Claude in Cursor) to pause their workflow and **ask you directly** for clarification, preferences, or missing information through native GUI dialogs. 12 | 13 | ### The Problem It Solves 14 | 15 | When AI assistants encounter situations where they need human input to proceed effectively, they typically either: 16 | 17 | - Make assumptions that might be wrong 18 | - Ask generic questions in the chat 19 | - Get stuck without clear direction 20 | 21 | This MCP server enables **true human-in-the-loop workflows** where the AI can: 22 | 23 | - ✅ Pause and ask for specific clarification 24 | - ✅ Present context about why information is needed 25 | - ✅ Get immediate, focused responses through native dialogs 26 | - ✅ Continue with confidence based on your input 27 | 28 | ## 🎯 Use Cases 29 | 30 | Perfect for scenarios where AI needs human guidance: 31 | 32 | - **Multiple Implementation Approaches**: "Should I use React or Vue for this component?" 33 | - **Technology Preferences**: "Which database would you prefer: PostgreSQL or MongoDB?" 34 | - **Domain-Specific Requirements**: "What's the maximum file size for uploads in your system?" 35 | - **User Experience Decisions**: "How should we handle errors - modal dialogs or inline messages?" 36 | - **Code Architecture**: "Should this be a microservice or part of the monolith?" 37 | - **Missing Context**: "What's the expected behavior when the API is down?" 38 | 39 | ## 🚀 Quick Start with Cursor 40 | 41 | ### 1. Add to Cursor MCP Configuration 42 | 43 | Add this to your Cursor MCP settings (`~/.cursor/mcp.json`): 44 | 45 | ```json 46 | { 47 | "mcpServers": { 48 | "ask-human-for-context": { 49 | "command": "uvx", 50 | "args": ["ask-human-for-context-mcp", "--transport", "stdio"] 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | > **Note**: No manual installation needed! uvx automatically downloads and runs the package from PyPI. 57 | 58 | ### 2. Restart Cursor 59 | 60 | The MCP server will now be available to Claude in your Cursor sessions! 61 | 62 | ## 💬 How It Works 63 | 64 | ### For AI Assistants 65 | 66 | When Claude (or another AI) needs human input, it can call the `asking_user_missing_context` tool: 67 | 68 | ```python 69 | # AI calls this tool when it needs clarification 70 | asking_user_missing_context( 71 | question="Should I implement authentication using JWT tokens or session cookies?", 72 | context="I'm building the login system for your web app. Both approaches are valid, but they have different security and performance trade-offs." 73 | ) 74 | ``` 75 | 76 | ### For Humans 77 | 78 | You'll see a native dialog box like this: 79 | 80 | ``` 81 | 📋 Missing Context: 82 | I'm building the login system for your web app. Both approaches are 83 | valid, but they have different security and performance trade-offs. 84 | 85 | ──────────────────────────────────────── 86 | 87 | ❓ Question: 88 | Should I implement authentication using JWT tokens or session cookies? 89 | 90 | [Text Input Field] 91 | [ OK ] [ Cancel ] 92 | ``` 93 | 94 | Your response gets sent back to the AI to continue the workflow. 95 | 96 | ## 🖥️ Platform Support 97 | 98 | ### Cross-Platform Native Dialogs 99 | 100 | | Platform | Technology | Features | 101 | | ----------- | ----------- | ------------------------------------- | 102 | | **macOS** | `osascript` | Custom Cursor icon, 90-second timeout | 103 | | **Linux** | `zenity` | Custom window icon, proper styling | 104 | | **Windows** | `tkinter` | Native Windows dialogs | 105 | 106 | ### Automatic Fallbacks 107 | 108 | - Graceful error handling if GUI systems aren't available 109 | - Clear error messages with troubleshooting guidance 110 | - No crashes or hanging - always responds to the AI 111 | 112 | ## 🔧 Installation Options 113 | 114 | ### Option 1: uvx (Recommended - Production Ready) 115 | 116 | Simply add to your Cursor MCP configuration - no manual installation required: 117 | 118 | ```json 119 | { 120 | "ask-human-for-context": { 121 | "command": "uvx", 122 | "args": ["ask-human-for-context-mcp", "--transport", "stdio"] 123 | } 124 | } 125 | ``` 126 | 127 | > **✨ Auto-Install**: uvx automatically downloads the latest version from PyPI! 128 | > **🔄 Auto-Update**: uvx handles version management and updates seamlessly. 129 | 130 | ### Option 2: pip + Virtual Environment 131 | 132 | ```bash 133 | # Create virtual environment 134 | python -m venv venv 135 | source venv/bin/activate # On Windows: venv\Scripts\activate 136 | 137 | # Install package 138 | pip install ask-human-for-context-mcp 139 | 140 | # Add to Cursor config 141 | { 142 | "ask-human-for-context": { 143 | "command": "/path/to/venv/bin/ask-human-for-context-mcp", 144 | "args": ["--transport", "stdio"] 145 | } 146 | } 147 | ``` 148 | 149 | ### Option 3: Development Installation 150 | 151 | ```bash 152 | # Clone and install for development 153 | git clone https://github.com/galperetz/ask-human-for-context-mcp.git 154 | cd ask-human-for-context-mcp 155 | pip install -e . 156 | 157 | # Use in Cursor config (local development) 158 | { 159 | "ask-human-for-context": { 160 | "command": "uvx", 161 | "args": ["--from", "/path/to/project", "ask-human-for-context-mcp", "--transport", "stdio"] 162 | } 163 | } 164 | ``` 165 | 166 | > **Note**: For production use, prefer Option 1 which uses the published PyPI package. 167 | 168 | ### Option 4: MCP Server Configuration File 169 | 170 | For easy management, you can use the included `mcp-server-config.json` file which provides multiple portable configuration options: 171 | 172 | ```json 173 | { 174 | "ask-human-for-context-uvx": { 175 | "command": "uvx", 176 | "args": ["ask-human-for-context-mcp", "--transport", "stdio"] 177 | }, 178 | "ask-human-for-context-pip": { 179 | "command": "ask-human-for-context-mcp", 180 | "args": ["--transport", "stdio"] 181 | }, 182 | "ask-human-for-context-python": { 183 | "command": "python", 184 | "args": ["-m", "ask_human_for_context_mcp", "--transport", "stdio"] 185 | }, 186 | "ask-human-for-context-local-dev": { 187 | "command": "python", 188 | "args": ["src/ask_human_for_context_mcp/server.py", "--transport", "stdio"], 189 | "cwd": ".", 190 | "env": { 191 | "PYTHONPATH": "./src" 192 | } 193 | } 194 | } 195 | ``` 196 | 197 | **Configuration Options Explained:** 198 | 199 | - **`uvx`**: ✅ **Recommended** - Auto-installs from PyPI, works globally 200 | - **`pip`**: For when package is installed via pip 201 | - **`python`**: Uses module execution, works if package is installed 202 | - **`local-dev`**: For development from source code with relative paths 203 | 204 | ## ⚙️ Configuration 205 | 206 | ### Transport Modes 207 | 208 | #### STDIO (Default) 209 | 210 | Perfect for MCP clients like Cursor: 211 | 212 | ```bash 213 | ask-human-for-context-mcp --transport stdio 214 | ``` 215 | 216 | #### SSE (Server-Sent Events) 217 | 218 | For web applications: 219 | 220 | ```bash 221 | ask-human-for-context-mcp --transport sse --host 0.0.0.0 --port 8080 222 | ``` 223 | 224 | ### Timeout Settings 225 | 226 | - **Default timeout**: 90 seconds (1.5 minutes) 227 | - **Configurable range**: 30 seconds to 2 hours 228 | - **User-friendly**: Shows timeout in minutes for better UX 229 | 230 | ## 🔍 Tool Reference 231 | 232 | ### `asking_user_missing_context` 233 | 234 | Ask the user for missing context during AI workflows. 235 | 236 | **Parameters:** 237 | 238 | - `question` (string, required): The specific question (max 1000 chars) 239 | - `context` (string, optional): Background explaining why context is needed (max 2000 chars) 240 | 241 | **Returns:** 242 | 243 | - `✅ User response: [user's answer]` - When user provides input 244 | - `⚠️ Empty response received` - When user clicks OK without entering text 245 | - `⚠️ Timeout: No response within [time]` - When dialog times out 246 | - `⚠️ Cancelled: User cancelled the prompt` - When user cancels dialog 247 | - `❌ Error: [description]` - When there are validation or system errors 248 | 249 | **Example Usage:** 250 | 251 | ```python 252 | # Simple question 253 | result = asking_user_missing_context( 254 | question="What's the preferred color scheme for the UI?" 255 | ) 256 | 257 | # Question with context 258 | result = asking_user_missing_context( 259 | question="Should I use REST or GraphQL for the API?", 260 | context="I'm designing the backend architecture. The frontend will need to fetch user data, posts, and comments. Performance and caching are important considerations." 261 | ) 262 | ``` 263 | 264 | ## 🛠️ Development 265 | 266 | ### Requirements 267 | 268 | - Python 3.10+ (supports 3.8+ but tested on 3.12) 269 | - Dependencies: `mcp`, `starlette`, `uvicorn` 270 | - Platform-specific: `osascript` (macOS), `zenity` (Linux), `tkinter` (Windows) 271 | 272 | ### Building 273 | 274 | ```bash 275 | # Install dependencies (using uv - recommended) 276 | uv sync 277 | 278 | # Or using pip 279 | pip install -e . 280 | 281 | # Build package 282 | uv build 283 | 284 | # Run tests 285 | pytest 286 | ``` 287 | 288 | ### Project Structure 289 | 290 | ``` 291 | ask-human-for-context-mcp/ 292 | ├── src/ask_human_for_context_mcp/ 293 | │ ├── __init__.py 294 | │ ├── __main__.py 295 | │ └── server.py # Main MCP server implementation 296 | ├── assets/ 297 | │ └── cursor-icon.icns # Custom Cursor icon for dialogs 298 | ├── pyproject.toml # Project configuration 299 | └── README.md 300 | ``` 301 | 302 | ## 🤝 Integration Examples 303 | 304 | ### Cursor AI Development Workflow 305 | 306 | 1. **AI encounters decision point**: "I need to choose between TypeScript and JavaScript" 307 | 2. **AI calls the tool**: Provides context about the project and asks for preference 308 | 3. **User sees dialog**: Native popup with formatted question and context 309 | 4. **User responds**: Types preference and clicks OK 310 | 5. **AI continues**: Uses the human input to make informed decisions 311 | 312 | ### Perfect for: 313 | 314 | - **Code reviews**: "Should I refactor this function or leave it as-is?" 315 | - **Architecture decisions**: "Microservices or monolith for this feature?" 316 | - **UI/UX choices**: "Modal dialog or inline editing for this form?" 317 | - **Technology selection**: "Which CSS framework fits your preferences?" 318 | 319 | ## 🔒 Security & Privacy 320 | 321 | - **Local execution**: All dialogs run locally on your machine 322 | - **No data collection**: No user responses are logged or transmitted 323 | - **Secure communication**: Uses MCP's secure transport protocols 324 | - **Timeout protection**: Automatic cleanup prevents hanging processes 325 | 326 | ## 📜 License 327 | 328 | MIT License - see [LICENSE](LICENSE) file for details. 329 | 330 | ## 🤝 Contributing 331 | 332 | Contributions are welcome! Please feel free to submit a Pull Request. 333 | 334 | 1. Fork the repository 335 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 336 | 3. Commit your changes (`git commit -m 'Add amazing feature'`) 337 | 4. Push to the branch (`git push origin feature/amazing-feature`) 338 | 5. Open a Pull Request 339 | 340 | ## 📞 Support 341 | 342 | - **Issues**: [GitHub Issues](https://github.com/galperetz/ask-human-for-context-mcp/issues) 343 | - **Documentation**: [Model Context Protocol](https://modelcontextprotocol.io/) 344 | - **MCP Community**: [MCP Discussions](https://github.com/modelcontextprotocol/specification/discussions) 345 | 346 | --- 347 | 348 | **Made with ❤️ for better human-AI collaboration** 349 | -------------------------------------------------------------------------------- /src/ask_human_for_context_mcp/server.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import subprocess 3 | import platform 4 | from typing import Any, Optional 5 | from mcp.server.fastmcp import FastMCP 6 | from starlette.applications import Starlette 7 | from mcp.server.sse import SseServerTransport 8 | from starlette.requests import Request 9 | from starlette.routing import Mount, Route 10 | from mcp.server import Server 11 | import uvicorn 12 | 13 | # Initialize FastMCP server for User Prompt tools 14 | mcp = FastMCP("ask-human-for-context") 15 | 16 | 17 | # Custom exception classes for better error handling (Task 1.4) 18 | class UserPromptTimeout(Exception): 19 | """Raised when user doesn't respond within timeout period.""" 20 | pass 21 | 22 | 23 | class UserPromptCancelled(Exception): 24 | """Raised when user cancels the prompt or interrupts the process.""" 25 | pass 26 | 27 | 28 | class UserPromptError(Exception): 29 | """Generic error for user prompt operations.""" 30 | pass 31 | 32 | 33 | class GUIDialogHandler: 34 | """Cross-platform GUI dialog handler for asking humans for context. 35 | 36 | Provides native GUI dialogs on macOS (osascript), Linux (zenity), and Windows (tkinter). 37 | Falls back to terminal input if GUI is unavailable. 38 | """ 39 | 40 | def __init__(self): 41 | """Initialize the dialog handler with platform detection.""" 42 | self.platform = platform.system() 43 | 44 | async def get_user_input(self, question: str, timeout: int = 1200) -> Optional[str]: 45 | """Get user input via native GUI dialog with timeout. 46 | 47 | Args: 48 | question: The question to ask the user 49 | timeout: Timeout in seconds (default: 1200 = 20 minutes) 50 | 51 | Returns: 52 | The user's response as a string, or None if timeout/cancelled 53 | 54 | Raises: 55 | UserPromptError: If GUI dialog system fails 56 | UserPromptCancelled: If user cancels or interrupts 57 | """ 58 | try: 59 | if self.platform == "Darwin": 60 | return await self._macos_dialog(question, timeout) 61 | elif self.platform == "Linux": 62 | return await self._linux_dialog(question, timeout) 63 | else: 64 | return await self._windows_dialog(question, timeout) 65 | except KeyboardInterrupt: 66 | # Handle Ctrl+C gracefully 67 | raise UserPromptCancelled("User interrupted the dialog with Ctrl+C") 68 | except Exception as e: 69 | # Don't fall back to terminal in MCP context - just report the error 70 | raise UserPromptError(f"GUI dialog failed: {e}. Ensure osascript (macOS), zenity (Linux), or tkinter (Windows) is available.") 71 | 72 | async def _macos_dialog(self, question: str, timeout: int) -> Optional[str]: 73 | """macOS dialog using osascript with custom Cursor icon.""" 74 | 75 | # Use the custom Cursor icon from assets folder 76 | import os 77 | 78 | # Use absolute path to the icon file - more reliable than path calculation 79 | cursor_icon_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "assets", "cursor-icon.icns") 80 | 81 | if os.path.exists(cursor_icon_path): 82 | icon_clause = f'with icon file (POSIX file "{cursor_icon_path}")' 83 | else: 84 | # Fallback to caution icon if custom icon not found 85 | icon_clause = "with icon caution" 86 | 87 | script = f''' 88 | display dialog "{self._escape_for_applescript(question)}" ¬ 89 | default answer "" ¬ 90 | with title "🤖 Cursor AI Assistant" ¬ 91 | {icon_clause} ¬ 92 | giving up after {timeout} 93 | ''' 94 | 95 | try: 96 | process = await asyncio.create_subprocess_exec( 97 | "osascript", "-e", script, 98 | stdout=asyncio.subprocess.PIPE, 99 | stderr=asyncio.subprocess.PIPE 100 | ) 101 | 102 | stdout, stderr = await process.communicate() 103 | 104 | if process.returncode == 0: 105 | output = stdout.decode().strip() 106 | # Handle AppleScript output format: "button returned:OK, text returned:user_input" 107 | if "text returned:" in output: 108 | # Extract text after "text returned:" and before any comma or end 109 | text_part = output.split("text returned:")[1] 110 | # Remove trailing ", gave up:false" or similar 111 | if ", " in text_part: 112 | return text_part.split(", ")[0].strip() 113 | return text_part.strip() 114 | elif "gave up:true" in output: 115 | # User didn't respond within timeout 116 | return None 117 | elif "button returned:" in output and "text returned:" not in output: 118 | # User clicked OK but didn't enter text 119 | return "" 120 | return None 121 | except Exception as e: 122 | return None 123 | 124 | async def _linux_dialog(self, question: str, timeout: int) -> Optional[str]: 125 | """Linux dialog using zenity with custom Cursor logo.""" 126 | # Use the custom Cursor logo for consistent branding 127 | icon_args = self._get_linux_icon_args() 128 | 129 | cmd = [ 130 | "zenity", "--entry", 131 | "--title=🤖 Cursor AI Assistant", 132 | f"--text={question}", 133 | f"--timeout={timeout}", 134 | ] + icon_args 135 | 136 | try: 137 | process = await asyncio.create_subprocess_exec( 138 | *cmd, 139 | stdout=asyncio.subprocess.PIPE, 140 | stderr=asyncio.subprocess.PIPE 141 | ) 142 | 143 | stdout, stderr = await process.communicate() 144 | 145 | if process.returncode == 0: 146 | return stdout.decode().strip() 147 | return None 148 | except Exception: 149 | return None 150 | 151 | async def _windows_dialog(self, question: str, timeout: int) -> Optional[str]: 152 | """Windows dialog using tkinter with custom Cursor logo.""" 153 | try: 154 | import tkinter as tk 155 | from tkinter import simpledialog 156 | import os 157 | 158 | # Create a simple dialog using tkinter 159 | root = tk.Tk() 160 | root.withdraw() # Hide the main window 161 | 162 | # Try to set custom icon from PNG (converted to ICO) 163 | self._set_windows_icon(root) 164 | 165 | result = simpledialog.askstring("🤖 Cursor AI Assistant", question) 166 | root.destroy() 167 | 168 | return result 169 | except Exception: 170 | return None 171 | 172 | def _escape_for_applescript(self, text: str) -> str: 173 | """Escape text for AppleScript.""" 174 | return text.replace('"', '\\"').replace('\\', '\\\\') 175 | 176 | def _get_macos_icon_clause(self) -> str: 177 | """Get the icon clause for macOS dialog with custom Cursor logo.""" 178 | import os 179 | 180 | # Check for the specific Cursor Logo files (prioritize ICNS for macOS) 181 | custom_logo_paths = [ 182 | os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "assets", "cursor-icon.icns"), # Try ICNS first (native macOS format) 183 | "./cursor-icon.icns", 184 | "./assets/Cursor Logo (4).png", 185 | "./Cursor Logo (4).png" 186 | ] 187 | 188 | for icon_path in custom_logo_paths: 189 | if os.path.exists(icon_path): 190 | if icon_path.endswith('.icns'): 191 | print(f"✅ Found ICNS icon: {icon_path}") 192 | abs_path = os.path.abspath(icon_path) 193 | return f'with icon file (POSIX file "{abs_path}")' 194 | elif icon_path.endswith('.png'): 195 | print(f"✅ Found custom Cursor logo: {icon_path}") 196 | print("ℹ️ Note: Using application icon style for PNG logo") 197 | # Use 'application' icon for software/AI assistant feel 198 | return "with icon application" 199 | 200 | # Fall back to application icon (better for AI assistant than default) 201 | return "with icon application" 202 | 203 | def _get_linux_icon_args(self) -> list: 204 | """Get icon arguments for Linux zenity dialog with custom Cursor logo.""" 205 | import os 206 | 207 | # Check for the specific Cursor Logo (4).png file first 208 | custom_logo_paths = [ 209 | "./assets/Cursor Logo (4).png", 210 | "./Cursor Logo (4).png", 211 | "./assets/cursor-icon.png", 212 | "./cursor-icon.png" 213 | ] 214 | 215 | for icon_path in custom_logo_paths: 216 | if os.path.exists(icon_path): 217 | print(f"✅ Using custom Cursor logo for Linux: {icon_path}") 218 | return [f"--window-icon={icon_path}"] 219 | 220 | # Fall back to built-in question icon 221 | return ["--question"] 222 | 223 | def _set_windows_icon(self, root) -> None: 224 | """Set icon for Windows tkinter dialog with custom Cursor logo.""" 225 | import os 226 | 227 | # Check for custom Cursor icon files 228 | possible_icon_paths = [ 229 | "./assets/cursor-icon.ico", 230 | "./cursor-icon.ico", 231 | "C:\\Program Files\\Cursor\\cursor.ico" 232 | ] 233 | 234 | for icon_path in possible_icon_paths: 235 | if os.path.exists(icon_path): 236 | try: 237 | print(f"✅ Using custom Cursor icon for Windows: {icon_path}") 238 | root.iconbitmap(icon_path) 239 | return 240 | except Exception: 241 | continue 242 | 243 | # Note: PNG files can't be directly used as Windows icons 244 | # Users would need to convert "Cursor Logo (4).png" to ICO format 245 | if os.path.exists("./assets/Cursor Logo (4).png"): 246 | print("ℹ️ Found PNG logo. For Windows, convert to ICO format for icon support.") 247 | 248 | 249 | # Global dialog handler instance 250 | dialog_handler = GUIDialogHandler() 251 | 252 | 253 | @mcp.tool() 254 | async def asking_user_missing_context( 255 | question: str, 256 | context: str = "" 257 | ) -> str: 258 | """Ask the user to fill missing context or knowledge gaps during research and development. 259 | 260 | This tool enables AI assistants to pause workflows when they encounter missing context, 261 | need clarification on implementation choices, or require understanding of preferred 262 | approaches. Use this when conducting research and you need user input to proceed effectively. 263 | 264 | Common use cases: 265 | - Multiple valid implementation approaches exist (ask user for preference) 266 | - Need clarification on preferred tech stack or framework 267 | - Missing domain-specific requirements or constraints 268 | - Uncertain about user's specific goals or priorities 269 | - Need to understand existing codebase patterns or conventions 270 | 271 | Args: 272 | question: The specific question about missing context (max 1000 characters) 273 | context: Background info explaining why this context is needed (max 2000 characters) 274 | 275 | Returns: 276 | The user's response as a formatted string with status indicator 277 | 278 | Raises: 279 | ValueError: If parameters are invalid or out of acceptable ranges 280 | """ 281 | 282 | timeout_seconds = 90 # 1.5 minutes 283 | 284 | # Parameter validation with clear error messages 285 | if not question or not isinstance(question, str): 286 | return "❌ Error: 'question' parameter is required and must be a non-empty string" 287 | 288 | if len(question.strip()) == 0: 289 | return "❌ Error: 'question' cannot be empty or only whitespace" 290 | 291 | if len(question) > 1000: 292 | return "❌ Error: 'question' is too long (max 1000 characters). Please shorten your question." 293 | 294 | if not isinstance(timeout_seconds, int): 295 | return "❌ Error: 'timeout_seconds' must be an integer" 296 | 297 | if timeout_seconds < 30: 298 | return "❌ Error: 'timeout_seconds' must be at least 30 seconds for usability" 299 | 300 | if timeout_seconds > 7200: # 2 hours max 301 | return "❌ Error: 'timeout_seconds' cannot exceed 7200 seconds (2 hours) to prevent indefinite hanging" 302 | 303 | if not isinstance(context, str): 304 | return "❌ Error: 'context' must be a string (use empty string if no context needed)" 305 | 306 | if len(context) > 2000: 307 | return "❌ Error: 'context' is too long (max 2000 characters). Please provide a more concise context." 308 | 309 | try: 310 | # Format question with context for better user experience 311 | if context.strip(): 312 | # Format context clearly with visual separation 313 | formatted_context = f"📋 Missing Context:\n{context.strip()}\n" 314 | separator = "─" * 40 315 | full_question = f"{formatted_context}\n{separator}\n\n❓ Question:\n{question.strip()}" 316 | else: 317 | full_question = f"❓ {question.strip()}" 318 | 319 | # Get user input via GUI dialog with timeout 320 | response = await dialog_handler.get_user_input(full_question, timeout_seconds) 321 | 322 | # Handle different response scenarios with custom exceptions 323 | if response is None: 324 | # Timeout occurred 325 | timeout_minutes = timeout_seconds // 60 326 | timeout_display = f"{timeout_minutes} minute{'s' if timeout_minutes != 1 else ''}" 327 | raise UserPromptTimeout(f"No response received within {timeout_display}") 328 | 329 | if not response.strip(): 330 | # Empty response (user clicked OK without entering text) 331 | return "⚠️ Empty response received. The user clicked OK but didn't enter any text. Please ask again if a response is needed." 332 | 333 | # Successful response 334 | clean_response = response.strip() 335 | 336 | # Format response with clear indicator 337 | return f"✅ User response: {clean_response}" 338 | 339 | except UserPromptTimeout as e: 340 | # Handle timeout with user-friendly message 341 | return f"⚠️ Timeout: {str(e)}. The dialog may have timed out or been cancelled. Please try asking again if needed." 342 | 343 | except UserPromptCancelled as e: 344 | # Handle cancellation 345 | return f"⚠️ Cancelled: {str(e)}. The user cancelled the prompt. Please try again or rephrase your question." 346 | 347 | except KeyboardInterrupt: 348 | # Handle Ctrl+C gracefully without crashing the server 349 | raise UserPromptCancelled("User interrupted the prompt with Ctrl+C") 350 | 351 | except UserPromptError as e: 352 | # Handle custom user prompt errors 353 | return f"❌ User Prompt Error: {str(e)}" 354 | 355 | except Exception as e: 356 | # Comprehensive error handling with helpful context 357 | error_context = f"Question: {question[:100]}{'...' if len(question) > 100 else ''}" 358 | return f"❌ Error getting user input: {str(e)}\n\nContext: {error_context}\n\nThe GUI dialog system may not be available. Check that the required dependencies are installed (zenity on Linux, osascript on macOS, tkinter on Windows)." 359 | 360 | 361 | def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette: 362 | """Create a Starlette application that can serve the provided MCP server with SSE. 363 | 364 | Sets up a Starlette web application with routes for SSE (Server-Sent Events) 365 | communication with the MCP server. 366 | 367 | Args: 368 | mcp_server: The MCP server instance to connect 369 | debug: Whether to enable debug mode for the Starlette app 370 | 371 | Returns: 372 | A configured Starlette application 373 | """ 374 | # Create an SSE transport with a base path for messages 375 | sse = SseServerTransport("/messages/") 376 | 377 | async def handle_sse(request: Request) -> None: 378 | """Handler for SSE connections. 379 | 380 | Establishes an SSE connection and connects it to the MCP server. 381 | 382 | Args: 383 | request: The incoming HTTP request 384 | """ 385 | # Connect the SSE transport to the request 386 | async with sse.connect_sse( 387 | request.scope, 388 | request.receive, 389 | request._send, # noqa: SLF001 390 | ) as (read_stream, write_stream): 391 | # Run the MCP server with the SSE streams 392 | await mcp_server.run( 393 | read_stream, 394 | write_stream, 395 | mcp_server.create_initialization_options(), 396 | ) 397 | 398 | # Create and return the Starlette application with routes 399 | return Starlette( 400 | debug=debug, 401 | routes=[ 402 | Route("/sse", endpoint=handle_sse), # Endpoint for SSE connections 403 | Mount("/messages/", app=sse.handle_post_message), # Endpoint for posting messages 404 | ], 405 | ) 406 | 407 | 408 | def main(): 409 | """Main entry point for the User Prompt MCP server. 410 | 411 | This function serves as the primary entry point when the server is launched 412 | via uvx or direct Python execution. It handles argument parsing and server startup. 413 | """ 414 | # Get the underlying MCP server from the FastMCP instance 415 | mcp_server = mcp._mcp_server # noqa: WPS437 416 | 417 | import argparse 418 | 419 | # Set up command-line argument parsing 420 | parser = argparse.ArgumentParser(description='Run User Prompt MCP server with configurable transport') 421 | # Allow choosing between stdio and SSE transport modes 422 | parser.add_argument('--transport', choices=['stdio', 'sse'], default='stdio', 423 | help='Transport mode (stdio or sse)') 424 | # Host configuration for SSE mode 425 | parser.add_argument('--host', default='0.0.0.0', 426 | help='Host to bind to (for SSE mode)') 427 | # Port configuration for SSE mode 428 | parser.add_argument('--port', type=int, default=8080, 429 | help='Port to listen on (for SSE mode)') 430 | args = parser.parse_args() 431 | 432 | # Launch the server with the selected transport mode 433 | if args.transport == 'stdio': 434 | # Run with stdio transport (default) 435 | # This mode communicates through standard input/output 436 | mcp.run(transport='stdio') 437 | else: 438 | # Run with SSE transport (web-based) 439 | # Create a Starlette app to serve the MCP server 440 | starlette_app = create_starlette_app(mcp_server, debug=True) 441 | # Start the web server with the configured host and port 442 | uvicorn.run(starlette_app, host=args.host, port=args.port) 443 | 444 | 445 | if __name__ == "__main__": 446 | main() -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | revision = 1 3 | requires-python = ">=3.10" 4 | 5 | [[package]] 6 | name = "annotated-types" 7 | version = "0.7.0" 8 | source = { registry = "https://pypi.org/simple" } 9 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 10 | wheels = [ 11 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.9.0" 17 | source = { registry = "https://pypi.org/simple" } 18 | dependencies = [ 19 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 20 | { name = "idna" }, 21 | { name = "sniffio" }, 22 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 23 | ] 24 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 } 25 | wheels = [ 26 | { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 }, 27 | ] 28 | 29 | [[package]] 30 | name = "ask-human-for-context-mcp" 31 | version = "1.0.3" 32 | source = { editable = "." } 33 | dependencies = [ 34 | { name = "mcp" }, 35 | { name = "starlette" }, 36 | { name = "uvicorn" }, 37 | ] 38 | 39 | [package.optional-dependencies] 40 | dev = [ 41 | { name = "black" }, 42 | { name = "isort" }, 43 | { name = "mypy" }, 44 | { name = "pytest" }, 45 | ] 46 | 47 | [package.dev-dependencies] 48 | dev = [ 49 | { name = "pytest" }, 50 | ] 51 | 52 | [package.metadata] 53 | requires-dist = [ 54 | { name = "black", marker = "extra == 'dev'", specifier = ">=22.0.0" }, 55 | { name = "isort", marker = "extra == 'dev'", specifier = ">=5.0.0" }, 56 | { name = "mcp" }, 57 | { name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" }, 58 | { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, 59 | { name = "starlette" }, 60 | { name = "uvicorn" }, 61 | ] 62 | provides-extras = ["dev"] 63 | 64 | [package.metadata.requires-dev] 65 | dev = [{ name = "pytest", specifier = ">=8.3.5" }] 66 | 67 | [[package]] 68 | name = "black" 69 | version = "25.1.0" 70 | source = { registry = "https://pypi.org/simple" } 71 | dependencies = [ 72 | { name = "click" }, 73 | { name = "mypy-extensions" }, 74 | { name = "packaging" }, 75 | { name = "pathspec" }, 76 | { name = "platformdirs" }, 77 | { name = "tomli", marker = "python_full_version < '3.11'" }, 78 | { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 79 | ] 80 | sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 } 81 | wheels = [ 82 | { url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419 }, 83 | { url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080 }, 84 | { url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886 }, 85 | { url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404 }, 86 | { url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 }, 87 | { url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 }, 88 | { url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 }, 89 | { url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 }, 90 | { url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 }, 91 | { url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 }, 92 | { url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 }, 93 | { url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 }, 94 | { url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 }, 95 | { url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 }, 96 | { url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 }, 97 | { url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 }, 98 | { url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 }, 99 | ] 100 | 101 | [[package]] 102 | name = "certifi" 103 | version = "2025.4.26" 104 | source = { registry = "https://pypi.org/simple" } 105 | sdist = { url = "https://files.pythonhosted.org/packages/e8/9e/c05b3920a3b7d20d3d3310465f50348e5b3694f4f88c6daf736eef3024c4/certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", size = 160705 } 106 | wheels = [ 107 | { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, 108 | ] 109 | 110 | [[package]] 111 | name = "click" 112 | version = "8.2.1" 113 | source = { registry = "https://pypi.org/simple" } 114 | dependencies = [ 115 | { name = "colorama", marker = "sys_platform == 'win32'" }, 116 | ] 117 | sdist = { url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", size = 286342 } 118 | wheels = [ 119 | { url = "https://files.pythonhosted.org/packages/85/32/10bb5764d90a8eee674e9dc6f4db6a0ab47c8c4d0d83c27f7c39ac415a4d/click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b", size = 102215 }, 120 | ] 121 | 122 | [[package]] 123 | name = "colorama" 124 | version = "0.4.6" 125 | source = { registry = "https://pypi.org/simple" } 126 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 127 | wheels = [ 128 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 129 | ] 130 | 131 | [[package]] 132 | name = "exceptiongroup" 133 | version = "1.3.0" 134 | source = { registry = "https://pypi.org/simple" } 135 | dependencies = [ 136 | { name = "typing-extensions", marker = "python_full_version < '3.13'" }, 137 | ] 138 | sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749 } 139 | wheels = [ 140 | { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674 }, 141 | ] 142 | 143 | [[package]] 144 | name = "h11" 145 | version = "0.16.0" 146 | source = { registry = "https://pypi.org/simple" } 147 | sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } 148 | wheels = [ 149 | { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, 150 | ] 151 | 152 | [[package]] 153 | name = "httpcore" 154 | version = "1.0.9" 155 | source = { registry = "https://pypi.org/simple" } 156 | dependencies = [ 157 | { name = "certifi" }, 158 | { name = "h11" }, 159 | ] 160 | sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } 161 | wheels = [ 162 | { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, 163 | ] 164 | 165 | [[package]] 166 | name = "httpx" 167 | version = "0.28.1" 168 | source = { registry = "https://pypi.org/simple" } 169 | dependencies = [ 170 | { name = "anyio" }, 171 | { name = "certifi" }, 172 | { name = "httpcore" }, 173 | { name = "idna" }, 174 | ] 175 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } 176 | wheels = [ 177 | { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, 178 | ] 179 | 180 | [[package]] 181 | name = "httpx-sse" 182 | version = "0.4.0" 183 | source = { registry = "https://pypi.org/simple" } 184 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 185 | wheels = [ 186 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 187 | ] 188 | 189 | [[package]] 190 | name = "idna" 191 | version = "3.10" 192 | source = { registry = "https://pypi.org/simple" } 193 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 194 | wheels = [ 195 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 196 | ] 197 | 198 | [[package]] 199 | name = "iniconfig" 200 | version = "2.1.0" 201 | source = { registry = "https://pypi.org/simple" } 202 | sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793 } 203 | wheels = [ 204 | { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050 }, 205 | ] 206 | 207 | [[package]] 208 | name = "isort" 209 | version = "6.0.1" 210 | source = { registry = "https://pypi.org/simple" } 211 | sdist = { url = "https://files.pythonhosted.org/packages/b8/21/1e2a441f74a653a144224d7d21afe8f4169e6c7c20bb13aec3a2dc3815e0/isort-6.0.1.tar.gz", hash = "sha256:1cb5df28dfbc742e490c5e41bad6da41b805b0a8be7bc93cd0fb2a8a890ac450", size = 821955 } 212 | wheels = [ 213 | { url = "https://files.pythonhosted.org/packages/c1/11/114d0a5f4dabbdcedc1125dee0888514c3c3b16d3e9facad87ed96fad97c/isort-6.0.1-py3-none-any.whl", hash = "sha256:2dc5d7f65c9678d94c88dfc29161a320eec67328bc97aad576874cb4be1e9615", size = 94186 }, 214 | ] 215 | 216 | [[package]] 217 | name = "mcp" 218 | version = "1.9.2" 219 | source = { registry = "https://pypi.org/simple" } 220 | dependencies = [ 221 | { name = "anyio" }, 222 | { name = "httpx" }, 223 | { name = "httpx-sse" }, 224 | { name = "pydantic" }, 225 | { name = "pydantic-settings" }, 226 | { name = "python-multipart" }, 227 | { name = "sse-starlette" }, 228 | { name = "starlette" }, 229 | { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, 230 | ] 231 | sdist = { url = "https://files.pythonhosted.org/packages/ea/03/77c49cce3ace96e6787af624611b627b2828f0dca0f8df6f330a10eea51e/mcp-1.9.2.tar.gz", hash = "sha256:3c7651c053d635fd235990a12e84509fe32780cd359a5bbef352e20d4d963c05", size = 333066 } 232 | wheels = [ 233 | { url = "https://files.pythonhosted.org/packages/5d/a6/8f5ee9da9f67c0fd8933f63d6105f02eabdac8a8c0926728368ffbb6744d/mcp-1.9.2-py3-none-any.whl", hash = "sha256:bc29f7fd67d157fef378f89a4210384f5fecf1168d0feb12d22929818723f978", size = 131083 }, 234 | ] 235 | 236 | [[package]] 237 | name = "mypy" 238 | version = "1.16.0" 239 | source = { registry = "https://pypi.org/simple" } 240 | dependencies = [ 241 | { name = "mypy-extensions" }, 242 | { name = "pathspec" }, 243 | { name = "tomli", marker = "python_full_version < '3.11'" }, 244 | { name = "typing-extensions" }, 245 | ] 246 | sdist = { url = "https://files.pythonhosted.org/packages/d4/38/13c2f1abae94d5ea0354e146b95a1be9b2137a0d506728e0da037c4276f6/mypy-1.16.0.tar.gz", hash = "sha256:84b94283f817e2aa6350a14b4a8fb2a35a53c286f97c9d30f53b63620e7af8ab", size = 3323139 } 247 | wheels = [ 248 | { url = "https://files.pythonhosted.org/packages/64/5e/a0485f0608a3d67029d3d73cec209278b025e3493a3acfda3ef3a88540fd/mypy-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7909541fef256527e5ee9c0a7e2aeed78b6cda72ba44298d1334fe7881b05c5c", size = 10967416 }, 249 | { url = "https://files.pythonhosted.org/packages/4b/53/5837c221f74c0d53a4bfc3003296f8179c3a2a7f336d7de7bbafbe96b688/mypy-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e71d6f0090c2256c713ed3d52711d01859c82608b5d68d4fa01a3fe30df95571", size = 10087654 }, 250 | { url = "https://files.pythonhosted.org/packages/29/59/5fd2400352c3093bed4c09017fe671d26bc5bb7e6ef2d4bf85f2a2488104/mypy-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:936ccfdd749af4766be824268bfe22d1db9eb2f34a3ea1d00ffbe5b5265f5491", size = 11875192 }, 251 | { url = "https://files.pythonhosted.org/packages/ad/3e/4bfec74663a64c2012f3e278dbc29ffe82b121bc551758590d1b6449ec0c/mypy-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4086883a73166631307fdd330c4a9080ce24913d4f4c5ec596c601b3a4bdd777", size = 12612939 }, 252 | { url = "https://files.pythonhosted.org/packages/88/1f/fecbe3dcba4bf2ca34c26ca016383a9676711907f8db4da8354925cbb08f/mypy-1.16.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:feec38097f71797da0231997e0de3a58108c51845399669ebc532c815f93866b", size = 12874719 }, 253 | { url = "https://files.pythonhosted.org/packages/f3/51/c2d280601cd816c43dfa512a759270d5a5ef638d7ac9bea9134c8305a12f/mypy-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:09a8da6a0ee9a9770b8ff61b39c0bb07971cda90e7297f4213741b48a0cc8d93", size = 9487053 }, 254 | { url = "https://files.pythonhosted.org/packages/24/c4/ff2f79db7075c274fe85b5fff8797d29c6b61b8854c39e3b7feb556aa377/mypy-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9f826aaa7ff8443bac6a494cf743f591488ea940dd360e7dd330e30dd772a5ab", size = 10884498 }, 255 | { url = "https://files.pythonhosted.org/packages/02/07/12198e83006235f10f6a7808917376b5d6240a2fd5dce740fe5d2ebf3247/mypy-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:82d056e6faa508501af333a6af192c700b33e15865bda49611e3d7d8358ebea2", size = 10011755 }, 256 | { url = "https://files.pythonhosted.org/packages/f1/9b/5fd5801a72b5d6fb6ec0105ea1d0e01ab2d4971893076e558d4b6d6b5f80/mypy-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:089bedc02307c2548eb51f426e085546db1fa7dd87fbb7c9fa561575cf6eb1ff", size = 11800138 }, 257 | { url = "https://files.pythonhosted.org/packages/2e/81/a117441ea5dfc3746431e51d78a4aca569c677aa225bca2cc05a7c239b61/mypy-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a2322896003ba66bbd1318c10d3afdfe24e78ef12ea10e2acd985e9d684a666", size = 12533156 }, 258 | { url = "https://files.pythonhosted.org/packages/3f/38/88ec57c6c86014d3f06251e00f397b5a7daa6888884d0abf187e4f5f587f/mypy-1.16.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:021a68568082c5b36e977d54e8f1de978baf401a33884ffcea09bd8e88a98f4c", size = 12742426 }, 259 | { url = "https://files.pythonhosted.org/packages/bd/53/7e9d528433d56e6f6f77ccf24af6ce570986c2d98a5839e4c2009ef47283/mypy-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:54066fed302d83bf5128632d05b4ec68412e1f03ef2c300434057d66866cea4b", size = 9478319 }, 260 | { url = "https://files.pythonhosted.org/packages/70/cf/158e5055e60ca2be23aec54a3010f89dcffd788732634b344fc9cb1e85a0/mypy-1.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5436d11e89a3ad16ce8afe752f0f373ae9620841c50883dc96f8b8805620b13", size = 11062927 }, 261 | { url = "https://files.pythonhosted.org/packages/94/34/cfff7a56be1609f5d10ef386342ce3494158e4d506516890142007e6472c/mypy-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f2622af30bf01d8fc36466231bdd203d120d7a599a6d88fb22bdcb9dbff84090", size = 10083082 }, 262 | { url = "https://files.pythonhosted.org/packages/b3/7f/7242062ec6288c33d8ad89574df87c3903d394870e5e6ba1699317a65075/mypy-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d045d33c284e10a038f5e29faca055b90eee87da3fc63b8889085744ebabb5a1", size = 11828306 }, 263 | { url = "https://files.pythonhosted.org/packages/6f/5f/b392f7b4f659f5b619ce5994c5c43caab3d80df2296ae54fa888b3d17f5a/mypy-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b4968f14f44c62e2ec4a038c8797a87315be8df7740dc3ee8d3bfe1c6bf5dba8", size = 12702764 }, 264 | { url = "https://files.pythonhosted.org/packages/9b/c0/7646ef3a00fa39ac9bc0938626d9ff29d19d733011be929cfea59d82d136/mypy-1.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eb14a4a871bb8efb1e4a50360d4e3c8d6c601e7a31028a2c79f9bb659b63d730", size = 12896233 }, 265 | { url = "https://files.pythonhosted.org/packages/6d/38/52f4b808b3fef7f0ef840ee8ff6ce5b5d77381e65425758d515cdd4f5bb5/mypy-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:bd4e1ebe126152a7bbaa4daedd781c90c8f9643c79b9748caa270ad542f12bec", size = 9565547 }, 266 | { url = "https://files.pythonhosted.org/packages/97/9c/ca03bdbefbaa03b264b9318a98950a9c683e06472226b55472f96ebbc53d/mypy-1.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a9e056237c89f1587a3be1a3a70a06a698d25e2479b9a2f57325ddaaffc3567b", size = 11059753 }, 267 | { url = "https://files.pythonhosted.org/packages/36/92/79a969b8302cfe316027c88f7dc6fee70129490a370b3f6eb11d777749d0/mypy-1.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b07e107affb9ee6ce1f342c07f51552d126c32cd62955f59a7db94a51ad12c0", size = 10073338 }, 268 | { url = "https://files.pythonhosted.org/packages/14/9b/a943f09319167da0552d5cd722104096a9c99270719b1afeea60d11610aa/mypy-1.16.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c6fb60cbd85dc65d4d63d37cb5c86f4e3a301ec605f606ae3a9173e5cf34997b", size = 11827764 }, 269 | { url = "https://files.pythonhosted.org/packages/ec/64/ff75e71c65a0cb6ee737287c7913ea155845a556c64144c65b811afdb9c7/mypy-1.16.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7e32297a437cc915599e0578fa6bc68ae6a8dc059c9e009c628e1c47f91495d", size = 12701356 }, 270 | { url = "https://files.pythonhosted.org/packages/0a/ad/0e93c18987a1182c350f7a5fab70550852f9fabe30ecb63bfbe51b602074/mypy-1.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:afe420c9380ccec31e744e8baff0d406c846683681025db3531b32db56962d52", size = 12900745 }, 271 | { url = "https://files.pythonhosted.org/packages/28/5d/036c278d7a013e97e33f08c047fe5583ab4f1fc47c9a49f985f1cdd2a2d7/mypy-1.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:55f9076c6ce55dd3f8cd0c6fff26a008ca8e5131b89d5ba6d86bd3f47e736eeb", size = 9572200 }, 272 | { url = "https://files.pythonhosted.org/packages/99/a3/6ed10530dec8e0fdc890d81361260c9ef1f5e5c217ad8c9b21ecb2b8366b/mypy-1.16.0-py3-none-any.whl", hash = "sha256:29e1499864a3888bca5c1542f2d7232c6e586295183320caa95758fc84034031", size = 2265773 }, 273 | ] 274 | 275 | [[package]] 276 | name = "mypy-extensions" 277 | version = "1.1.0" 278 | source = { registry = "https://pypi.org/simple" } 279 | sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 } 280 | wheels = [ 281 | { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 }, 282 | ] 283 | 284 | [[package]] 285 | name = "packaging" 286 | version = "25.0" 287 | source = { registry = "https://pypi.org/simple" } 288 | sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727 } 289 | wheels = [ 290 | { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, 291 | ] 292 | 293 | [[package]] 294 | name = "pathspec" 295 | version = "0.12.1" 296 | source = { registry = "https://pypi.org/simple" } 297 | sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } 298 | wheels = [ 299 | { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, 300 | ] 301 | 302 | [[package]] 303 | name = "platformdirs" 304 | version = "4.3.8" 305 | source = { registry = "https://pypi.org/simple" } 306 | sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 } 307 | wheels = [ 308 | { url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 }, 309 | ] 310 | 311 | [[package]] 312 | name = "pluggy" 313 | version = "1.6.0" 314 | source = { registry = "https://pypi.org/simple" } 315 | sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } 316 | wheels = [ 317 | { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, 318 | ] 319 | 320 | [[package]] 321 | name = "pydantic" 322 | version = "2.11.5" 323 | source = { registry = "https://pypi.org/simple" } 324 | dependencies = [ 325 | { name = "annotated-types" }, 326 | { name = "pydantic-core" }, 327 | { name = "typing-extensions" }, 328 | { name = "typing-inspection" }, 329 | ] 330 | sdist = { url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz", hash = "sha256:7f853db3d0ce78ce8bbb148c401c2cdd6431b3473c0cdff2755c7690952a7b7a", size = 787102 } 331 | wheels = [ 332 | { url = "https://files.pythonhosted.org/packages/b5/69/831ed22b38ff9b4b64b66569f0e5b7b97cf3638346eb95a2147fdb49ad5f/pydantic-2.11.5-py3-none-any.whl", hash = "sha256:f9c26ba06f9747749ca1e5c94d6a85cb84254577553c8785576fd38fa64dc0f7", size = 444229 }, 333 | ] 334 | 335 | [[package]] 336 | name = "pydantic-core" 337 | version = "2.33.2" 338 | source = { registry = "https://pypi.org/simple" } 339 | dependencies = [ 340 | { name = "typing-extensions" }, 341 | ] 342 | sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195 } 343 | wheels = [ 344 | { url = "https://files.pythonhosted.org/packages/e5/92/b31726561b5dae176c2d2c2dc43a9c5bfba5d32f96f8b4c0a600dd492447/pydantic_core-2.33.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2b3d326aaef0c0399d9afffeb6367d5e26ddc24d351dbc9c636840ac355dc5d8", size = 2028817 }, 345 | { url = "https://files.pythonhosted.org/packages/a3/44/3f0b95fafdaca04a483c4e685fe437c6891001bf3ce8b2fded82b9ea3aa1/pydantic_core-2.33.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e5b2671f05ba48b94cb90ce55d8bdcaaedb8ba00cc5359f6810fc918713983d", size = 1861357 }, 346 | { url = "https://files.pythonhosted.org/packages/30/97/e8f13b55766234caae05372826e8e4b3b96e7b248be3157f53237682e43c/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0069c9acc3f3981b9ff4cdfaf088e98d83440a4c7ea1bc07460af3d4dc22e72d", size = 1898011 }, 347 | { url = "https://files.pythonhosted.org/packages/9b/a3/99c48cf7bafc991cc3ee66fd544c0aae8dc907b752f1dad2d79b1b5a471f/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d53b22f2032c42eaaf025f7c40c2e3b94568ae077a606f006d206a463bc69572", size = 1982730 }, 348 | { url = "https://files.pythonhosted.org/packages/de/8e/a5b882ec4307010a840fb8b58bd9bf65d1840c92eae7534c7441709bf54b/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0405262705a123b7ce9f0b92f123334d67b70fd1f20a9372b907ce1080c7ba02", size = 2136178 }, 349 | { url = "https://files.pythonhosted.org/packages/e4/bb/71e35fc3ed05af6834e890edb75968e2802fe98778971ab5cba20a162315/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b25d91e288e2c4e0662b8038a28c6a07eaac3e196cfc4ff69de4ea3db992a1b", size = 2736462 }, 350 | { url = "https://files.pythonhosted.org/packages/31/0d/c8f7593e6bc7066289bbc366f2235701dcbebcd1ff0ef8e64f6f239fb47d/pydantic_core-2.33.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bdfe4b3789761f3bcb4b1ddf33355a71079858958e3a552f16d5af19768fef2", size = 2005652 }, 351 | { url = "https://files.pythonhosted.org/packages/d2/7a/996d8bd75f3eda405e3dd219ff5ff0a283cd8e34add39d8ef9157e722867/pydantic_core-2.33.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:efec8db3266b76ef9607c2c4c419bdb06bf335ae433b80816089ea7585816f6a", size = 2113306 }, 352 | { url = "https://files.pythonhosted.org/packages/ff/84/daf2a6fb2db40ffda6578a7e8c5a6e9c8affb251a05c233ae37098118788/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:031c57d67ca86902726e0fae2214ce6770bbe2f710dc33063187a68744a5ecac", size = 2073720 }, 353 | { url = "https://files.pythonhosted.org/packages/77/fb/2258da019f4825128445ae79456a5499c032b55849dbd5bed78c95ccf163/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:f8de619080e944347f5f20de29a975c2d815d9ddd8be9b9b7268e2e3ef68605a", size = 2244915 }, 354 | { url = "https://files.pythonhosted.org/packages/d8/7a/925ff73756031289468326e355b6fa8316960d0d65f8b5d6b3a3e7866de7/pydantic_core-2.33.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:73662edf539e72a9440129f231ed3757faab89630d291b784ca99237fb94db2b", size = 2241884 }, 355 | { url = "https://files.pythonhosted.org/packages/0b/b0/249ee6d2646f1cdadcb813805fe76265745c4010cf20a8eba7b0e639d9b2/pydantic_core-2.33.2-cp310-cp310-win32.whl", hash = "sha256:0a39979dcbb70998b0e505fb1556a1d550a0781463ce84ebf915ba293ccb7e22", size = 1910496 }, 356 | { url = "https://files.pythonhosted.org/packages/66/ff/172ba8f12a42d4b552917aa65d1f2328990d3ccfc01d5b7c943ec084299f/pydantic_core-2.33.2-cp310-cp310-win_amd64.whl", hash = "sha256:b0379a2b24882fef529ec3b4987cb5d003b9cda32256024e6fe1586ac45fc640", size = 1955019 }, 357 | { url = "https://files.pythonhosted.org/packages/3f/8d/71db63483d518cbbf290261a1fc2839d17ff89fce7089e08cad07ccfce67/pydantic_core-2.33.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4c5b0a576fb381edd6d27f0a85915c6daf2f8138dc5c267a57c08a62900758c7", size = 2028584 }, 358 | { url = "https://files.pythonhosted.org/packages/24/2f/3cfa7244ae292dd850989f328722d2aef313f74ffc471184dc509e1e4e5a/pydantic_core-2.33.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e799c050df38a639db758c617ec771fd8fb7a5f8eaaa4b27b101f266b216a246", size = 1855071 }, 359 | { url = "https://files.pythonhosted.org/packages/b3/d3/4ae42d33f5e3f50dd467761304be2fa0a9417fbf09735bc2cce003480f2a/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dc46a01bf8d62f227d5ecee74178ffc448ff4e5197c756331f71efcc66dc980f", size = 1897823 }, 360 | { url = "https://files.pythonhosted.org/packages/f4/f3/aa5976e8352b7695ff808599794b1fba2a9ae2ee954a3426855935799488/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a144d4f717285c6d9234a66778059f33a89096dfb9b39117663fd8413d582dcc", size = 1983792 }, 361 | { url = "https://files.pythonhosted.org/packages/d5/7a/cda9b5a23c552037717f2b2a5257e9b2bfe45e687386df9591eff7b46d28/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:73cf6373c21bc80b2e0dc88444f41ae60b2f070ed02095754eb5a01df12256de", size = 2136338 }, 362 | { url = "https://files.pythonhosted.org/packages/2b/9f/b8f9ec8dd1417eb9da784e91e1667d58a2a4a7b7b34cf4af765ef663a7e5/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dc625f4aa79713512d1976fe9f0bc99f706a9dee21dfd1810b4bbbf228d0e8a", size = 2730998 }, 363 | { url = "https://files.pythonhosted.org/packages/47/bc/cd720e078576bdb8255d5032c5d63ee5c0bf4b7173dd955185a1d658c456/pydantic_core-2.33.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b21b5549499972441da4758d662aeea93f1923f953e9cbaff14b8b9565aef", size = 2003200 }, 364 | { url = "https://files.pythonhosted.org/packages/ca/22/3602b895ee2cd29d11a2b349372446ae9727c32e78a94b3d588a40fdf187/pydantic_core-2.33.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bdc25f3681f7b78572699569514036afe3c243bc3059d3942624e936ec93450e", size = 2113890 }, 365 | { url = "https://files.pythonhosted.org/packages/ff/e6/e3c5908c03cf00d629eb38393a98fccc38ee0ce8ecce32f69fc7d7b558a7/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fe5b32187cbc0c862ee201ad66c30cf218e5ed468ec8dc1cf49dec66e160cc4d", size = 2073359 }, 366 | { url = "https://files.pythonhosted.org/packages/12/e7/6a36a07c59ebefc8777d1ffdaf5ae71b06b21952582e4b07eba88a421c79/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:bc7aee6f634a6f4a95676fcb5d6559a2c2a390330098dba5e5a5f28a2e4ada30", size = 2245883 }, 367 | { url = "https://files.pythonhosted.org/packages/16/3f/59b3187aaa6cc0c1e6616e8045b284de2b6a87b027cce2ffcea073adf1d2/pydantic_core-2.33.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:235f45e5dbcccf6bd99f9f472858849f73d11120d76ea8707115415f8e5ebebf", size = 2241074 }, 368 | { url = "https://files.pythonhosted.org/packages/e0/ed/55532bb88f674d5d8f67ab121a2a13c385df382de2a1677f30ad385f7438/pydantic_core-2.33.2-cp311-cp311-win32.whl", hash = "sha256:6368900c2d3ef09b69cb0b913f9f8263b03786e5b2a387706c5afb66800efd51", size = 1910538 }, 369 | { url = "https://files.pythonhosted.org/packages/fe/1b/25b7cccd4519c0b23c2dd636ad39d381abf113085ce4f7bec2b0dc755eb1/pydantic_core-2.33.2-cp311-cp311-win_amd64.whl", hash = "sha256:1e063337ef9e9820c77acc768546325ebe04ee38b08703244c1309cccc4f1bab", size = 1952909 }, 370 | { url = "https://files.pythonhosted.org/packages/49/a9/d809358e49126438055884c4366a1f6227f0f84f635a9014e2deb9b9de54/pydantic_core-2.33.2-cp311-cp311-win_arm64.whl", hash = "sha256:6b99022f1d19bc32a4c2a0d544fc9a76e3be90f0b3f4af413f87d38749300e65", size = 1897786 }, 371 | { url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000 }, 372 | { url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996 }, 373 | { url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957 }, 374 | { url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199 }, 375 | { url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296 }, 376 | { url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109 }, 377 | { url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028 }, 378 | { url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044 }, 379 | { url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881 }, 380 | { url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034 }, 381 | { url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187 }, 382 | { url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628 }, 383 | { url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866 }, 384 | { url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894 }, 385 | { url = "https://files.pythonhosted.org/packages/46/8c/99040727b41f56616573a28771b1bfa08a3d3fe74d3d513f01251f79f172/pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f", size = 2015688 }, 386 | { url = "https://files.pythonhosted.org/packages/3a/cc/5999d1eb705a6cefc31f0b4a90e9f7fc400539b1a1030529700cc1b51838/pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6", size = 1844808 }, 387 | { url = "https://files.pythonhosted.org/packages/6f/5e/a0a7b8885c98889a18b6e376f344da1ef323d270b44edf8174d6bce4d622/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef", size = 1885580 }, 388 | { url = "https://files.pythonhosted.org/packages/3b/2a/953581f343c7d11a304581156618c3f592435523dd9d79865903272c256a/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a", size = 1973859 }, 389 | { url = "https://files.pythonhosted.org/packages/e6/55/f1a813904771c03a3f97f676c62cca0c0a4138654107c1b61f19c644868b/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916", size = 2120810 }, 390 | { url = "https://files.pythonhosted.org/packages/aa/c3/053389835a996e18853ba107a63caae0b9deb4a276c6b472931ea9ae6e48/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a", size = 2676498 }, 391 | { url = "https://files.pythonhosted.org/packages/eb/3c/f4abd740877a35abade05e437245b192f9d0ffb48bbbbd708df33d3cda37/pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d", size = 2000611 }, 392 | { url = "https://files.pythonhosted.org/packages/59/a7/63ef2fed1837d1121a894d0ce88439fe3e3b3e48c7543b2a4479eb99c2bd/pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56", size = 2107924 }, 393 | { url = "https://files.pythonhosted.org/packages/04/8f/2551964ef045669801675f1cfc3b0d74147f4901c3ffa42be2ddb1f0efc4/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5", size = 2063196 }, 394 | { url = "https://files.pythonhosted.org/packages/26/bd/d9602777e77fc6dbb0c7db9ad356e9a985825547dce5ad1d30ee04903918/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e", size = 2236389 }, 395 | { url = "https://files.pythonhosted.org/packages/42/db/0e950daa7e2230423ab342ae918a794964b053bec24ba8af013fc7c94846/pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162", size = 2239223 }, 396 | { url = "https://files.pythonhosted.org/packages/58/4d/4f937099c545a8a17eb52cb67fe0447fd9a373b348ccfa9a87f141eeb00f/pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849", size = 1900473 }, 397 | { url = "https://files.pythonhosted.org/packages/a0/75/4a0a9bac998d78d889def5e4ef2b065acba8cae8c93696906c3a91f310ca/pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9", size = 1955269 }, 398 | { url = "https://files.pythonhosted.org/packages/f9/86/1beda0576969592f1497b4ce8e7bc8cbdf614c352426271b1b10d5f0aa64/pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9", size = 1893921 }, 399 | { url = "https://files.pythonhosted.org/packages/a4/7d/e09391c2eebeab681df2b74bfe6c43422fffede8dc74187b2b0bf6fd7571/pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac", size = 1806162 }, 400 | { url = "https://files.pythonhosted.org/packages/f1/3d/847b6b1fed9f8ed3bb95a9ad04fbd0b212e832d4f0f50ff4d9ee5a9f15cf/pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5", size = 1981560 }, 401 | { url = "https://files.pythonhosted.org/packages/6f/9a/e73262f6c6656262b5fdd723ad90f518f579b7bc8622e43a942eec53c938/pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9", size = 1935777 }, 402 | { url = "https://files.pythonhosted.org/packages/30/68/373d55e58b7e83ce371691f6eaa7175e3a24b956c44628eb25d7da007917/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5c4aa4e82353f65e548c476b37e64189783aa5384903bfea4f41580f255fddfa", size = 2023982 }, 403 | { url = "https://files.pythonhosted.org/packages/a4/16/145f54ac08c96a63d8ed6442f9dec17b2773d19920b627b18d4f10a061ea/pydantic_core-2.33.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d946c8bf0d5c24bf4fe333af284c59a19358aa3ec18cb3dc4370080da1e8ad29", size = 1858412 }, 404 | { url = "https://files.pythonhosted.org/packages/41/b1/c6dc6c3e2de4516c0bb2c46f6a373b91b5660312342a0cf5826e38ad82fa/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87b31b6846e361ef83fedb187bb5b4372d0da3f7e28d85415efa92d6125d6e6d", size = 1892749 }, 405 | { url = "https://files.pythonhosted.org/packages/12/73/8cd57e20afba760b21b742106f9dbdfa6697f1570b189c7457a1af4cd8a0/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa9d91b338f2df0508606f7009fde642391425189bba6d8c653afd80fd6bb64e", size = 2067527 }, 406 | { url = "https://files.pythonhosted.org/packages/e3/d5/0bb5d988cc019b3cba4a78f2d4b3854427fc47ee8ec8e9eaabf787da239c/pydantic_core-2.33.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2058a32994f1fde4ca0480ab9d1e75a0e8c87c22b53a3ae66554f9af78f2fe8c", size = 2108225 }, 407 | { url = "https://files.pythonhosted.org/packages/f1/c5/00c02d1571913d496aabf146106ad8239dc132485ee22efe08085084ff7c/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:0e03262ab796d986f978f79c943fc5f620381be7287148b8010b4097f79a39ec", size = 2069490 }, 408 | { url = "https://files.pythonhosted.org/packages/22/a8/dccc38768274d3ed3a59b5d06f59ccb845778687652daa71df0cab4040d7/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1a8695a8d00c73e50bff9dfda4d540b7dee29ff9b8053e38380426a85ef10052", size = 2237525 }, 409 | { url = "https://files.pythonhosted.org/packages/d4/e7/4f98c0b125dda7cf7ccd14ba936218397b44f50a56dd8c16a3091df116c3/pydantic_core-2.33.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:fa754d1850735a0b0e03bcffd9d4b4343eb417e47196e4485d9cca326073a42c", size = 2238446 }, 410 | { url = "https://files.pythonhosted.org/packages/ce/91/2ec36480fdb0b783cd9ef6795753c1dea13882f2e68e73bce76ae8c21e6a/pydantic_core-2.33.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a11c8d26a50bfab49002947d3d237abe4d9e4b5bdc8846a63537b6488e197808", size = 2066678 }, 411 | { url = "https://files.pythonhosted.org/packages/7b/27/d4ae6487d73948d6f20dddcd94be4ea43e74349b56eba82e9bdee2d7494c/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:dd14041875d09cc0f9308e37a6f8b65f5585cf2598a53aa0123df8b129d481f8", size = 2025200 }, 412 | { url = "https://files.pythonhosted.org/packages/f1/b8/b3cb95375f05d33801024079b9392a5ab45267a63400bf1866e7ce0f0de4/pydantic_core-2.33.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d87c561733f66531dced0da6e864f44ebf89a8fba55f31407b00c2f7f9449593", size = 1859123 }, 413 | { url = "https://files.pythonhosted.org/packages/05/bc/0d0b5adeda59a261cd30a1235a445bf55c7e46ae44aea28f7bd6ed46e091/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f82865531efd18d6e07a04a17331af02cb7a651583c418df8266f17a63c6612", size = 1892852 }, 414 | { url = "https://files.pythonhosted.org/packages/3e/11/d37bdebbda2e449cb3f519f6ce950927b56d62f0b84fd9cb9e372a26a3d5/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bfb5112df54209d820d7bf9317c7a6c9025ea52e49f46b6a2060104bba37de7", size = 2067484 }, 415 | { url = "https://files.pythonhosted.org/packages/8c/55/1f95f0a05ce72ecb02a8a8a1c3be0579bbc29b1d5ab68f1378b7bebc5057/pydantic_core-2.33.2-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:64632ff9d614e5eecfb495796ad51b0ed98c453e447a76bcbeeb69615079fc7e", size = 2108896 }, 416 | { url = "https://files.pythonhosted.org/packages/53/89/2b2de6c81fa131f423246a9109d7b2a375e83968ad0800d6e57d0574629b/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:f889f7a40498cc077332c7ab6b4608d296d852182211787d4f3ee377aaae66e8", size = 2069475 }, 417 | { url = "https://files.pythonhosted.org/packages/b8/e9/1f7efbe20d0b2b10f6718944b5d8ece9152390904f29a78e68d4e7961159/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:de4b83bb311557e439b9e186f733f6c645b9417c84e2eb8203f3f820a4b988bf", size = 2239013 }, 418 | { url = "https://files.pythonhosted.org/packages/3c/b2/5309c905a93811524a49b4e031e9851a6b00ff0fb668794472ea7746b448/pydantic_core-2.33.2-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:82f68293f055f51b51ea42fafc74b6aad03e70e191799430b90c13d643059ebb", size = 2238715 }, 419 | { url = "https://files.pythonhosted.org/packages/32/56/8a7ca5d2cd2cda1d245d34b1c9a942920a718082ae8e54e5f3e5a58b7add/pydantic_core-2.33.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:329467cecfb529c925cf2bbd4d60d2c509bc2fb52a20c1045bf09bb70971a9c1", size = 2066757 }, 420 | ] 421 | 422 | [[package]] 423 | name = "pydantic-settings" 424 | version = "2.9.1" 425 | source = { registry = "https://pypi.org/simple" } 426 | dependencies = [ 427 | { name = "pydantic" }, 428 | { name = "python-dotenv" }, 429 | { name = "typing-inspection" }, 430 | ] 431 | sdist = { url = "https://files.pythonhosted.org/packages/67/1d/42628a2c33e93f8e9acbde0d5d735fa0850f3e6a2f8cb1eb6c40b9a732ac/pydantic_settings-2.9.1.tar.gz", hash = "sha256:c509bf79d27563add44e8446233359004ed85066cd096d8b510f715e6ef5d268", size = 163234 } 432 | wheels = [ 433 | { url = "https://files.pythonhosted.org/packages/b6/5f/d6d641b490fd3ec2c4c13b4244d68deea3a1b970a97be64f34fb5504ff72/pydantic_settings-2.9.1-py3-none-any.whl", hash = "sha256:59b4f431b1defb26fe620c71a7d3968a710d719f5f4cdbbdb7926edeb770f6ef", size = 44356 }, 434 | ] 435 | 436 | [[package]] 437 | name = "pytest" 438 | version = "8.3.5" 439 | source = { registry = "https://pypi.org/simple" } 440 | dependencies = [ 441 | { name = "colorama", marker = "sys_platform == 'win32'" }, 442 | { name = "exceptiongroup", marker = "python_full_version < '3.11'" }, 443 | { name = "iniconfig" }, 444 | { name = "packaging" }, 445 | { name = "pluggy" }, 446 | { name = "tomli", marker = "python_full_version < '3.11'" }, 447 | ] 448 | sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891 } 449 | wheels = [ 450 | { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634 }, 451 | ] 452 | 453 | [[package]] 454 | name = "python-dotenv" 455 | version = "1.1.0" 456 | source = { registry = "https://pypi.org/simple" } 457 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 } 458 | wheels = [ 459 | { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 }, 460 | ] 461 | 462 | [[package]] 463 | name = "python-multipart" 464 | version = "0.0.20" 465 | source = { registry = "https://pypi.org/simple" } 466 | sdist = { url = "https://files.pythonhosted.org/packages/f3/87/f44d7c9f274c7ee665a29b885ec97089ec5dc034c7f3fafa03da9e39a09e/python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13", size = 37158 } 467 | wheels = [ 468 | { url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546 }, 469 | ] 470 | 471 | [[package]] 472 | name = "sniffio" 473 | version = "1.3.1" 474 | source = { registry = "https://pypi.org/simple" } 475 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 476 | wheels = [ 477 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 478 | ] 479 | 480 | [[package]] 481 | name = "sse-starlette" 482 | version = "2.3.6" 483 | source = { registry = "https://pypi.org/simple" } 484 | dependencies = [ 485 | { name = "anyio" }, 486 | ] 487 | sdist = { url = "https://files.pythonhosted.org/packages/8c/f4/989bc70cb8091eda43a9034ef969b25145291f3601703b82766e5172dfed/sse_starlette-2.3.6.tar.gz", hash = "sha256:0382336f7d4ec30160cf9ca0518962905e1b69b72d6c1c995131e0a703b436e3", size = 18284 } 488 | wheels = [ 489 | { url = "https://files.pythonhosted.org/packages/81/05/78850ac6e79af5b9508f8841b0f26aa9fd329a1ba00bf65453c2d312bcc8/sse_starlette-2.3.6-py3-none-any.whl", hash = "sha256:d49a8285b182f6e2228e2609c350398b2ca2c36216c2675d875f81e93548f760", size = 10606 }, 490 | ] 491 | 492 | [[package]] 493 | name = "starlette" 494 | version = "0.47.0" 495 | source = { registry = "https://pypi.org/simple" } 496 | dependencies = [ 497 | { name = "anyio" }, 498 | ] 499 | sdist = { url = "https://files.pythonhosted.org/packages/8b/d0/0332bd8a25779a0e2082b0e179805ad39afad642938b371ae0882e7f880d/starlette-0.47.0.tar.gz", hash = "sha256:1f64887e94a447fed5f23309fb6890ef23349b7e478faa7b24a851cd4eb844af", size = 2582856 } 500 | wheels = [ 501 | { url = "https://files.pythonhosted.org/packages/e3/81/c60b35fe9674f63b38a8feafc414fca0da378a9dbd5fa1e0b8d23fcc7a9b/starlette-0.47.0-py3-none-any.whl", hash = "sha256:9d052d4933683af40ffd47c7465433570b4949dc937e20ad1d73b34e72f10c37", size = 72796 }, 502 | ] 503 | 504 | [[package]] 505 | name = "tomli" 506 | version = "2.2.1" 507 | source = { registry = "https://pypi.org/simple" } 508 | sdist = { url = "https://files.pythonhosted.org/packages/18/87/302344fed471e44a87289cf4967697d07e532f2421fdaf868a303cbae4ff/tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff", size = 17175 } 509 | wheels = [ 510 | { url = "https://files.pythonhosted.org/packages/43/ca/75707e6efa2b37c77dadb324ae7d9571cb424e61ea73fad7c56c2d14527f/tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249", size = 131077 }, 511 | { url = "https://files.pythonhosted.org/packages/c7/16/51ae563a8615d472fdbffc43a3f3d46588c264ac4f024f63f01283becfbb/tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6", size = 123429 }, 512 | { url = "https://files.pythonhosted.org/packages/f1/dd/4f6cd1e7b160041db83c694abc78e100473c15d54620083dbd5aae7b990e/tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a", size = 226067 }, 513 | { url = "https://files.pythonhosted.org/packages/a9/6b/c54ede5dc70d648cc6361eaf429304b02f2871a345bbdd51e993d6cdf550/tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee", size = 236030 }, 514 | { url = "https://files.pythonhosted.org/packages/1f/47/999514fa49cfaf7a92c805a86c3c43f4215621855d151b61c602abb38091/tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e", size = 240898 }, 515 | { url = "https://files.pythonhosted.org/packages/73/41/0a01279a7ae09ee1573b423318e7934674ce06eb33f50936655071d81a24/tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4", size = 229894 }, 516 | { url = "https://files.pythonhosted.org/packages/55/18/5d8bc5b0a0362311ce4d18830a5d28943667599a60d20118074ea1b01bb7/tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106", size = 245319 }, 517 | { url = "https://files.pythonhosted.org/packages/92/a3/7ade0576d17f3cdf5ff44d61390d4b3febb8a9fc2b480c75c47ea048c646/tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8", size = 238273 }, 518 | { url = "https://files.pythonhosted.org/packages/72/6f/fa64ef058ac1446a1e51110c375339b3ec6be245af9d14c87c4a6412dd32/tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff", size = 98310 }, 519 | { url = "https://files.pythonhosted.org/packages/6a/1c/4a2dcde4a51b81be3530565e92eda625d94dafb46dbeb15069df4caffc34/tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b", size = 108309 }, 520 | { url = "https://files.pythonhosted.org/packages/52/e1/f8af4c2fcde17500422858155aeb0d7e93477a0d59a98e56cbfe75070fd0/tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea", size = 132762 }, 521 | { url = "https://files.pythonhosted.org/packages/03/b8/152c68bb84fc00396b83e7bbddd5ec0bd3dd409db4195e2a9b3e398ad2e3/tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8", size = 123453 }, 522 | { url = "https://files.pythonhosted.org/packages/c8/d6/fc9267af9166f79ac528ff7e8c55c8181ded34eb4b0e93daa767b8841573/tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192", size = 233486 }, 523 | { url = "https://files.pythonhosted.org/packages/5c/51/51c3f2884d7bab89af25f678447ea7d297b53b5a3b5730a7cb2ef6069f07/tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222", size = 242349 }, 524 | { url = "https://files.pythonhosted.org/packages/ab/df/bfa89627d13a5cc22402e441e8a931ef2108403db390ff3345c05253935e/tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77", size = 252159 }, 525 | { url = "https://files.pythonhosted.org/packages/9e/6e/fa2b916dced65763a5168c6ccb91066f7639bdc88b48adda990db10c8c0b/tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6", size = 237243 }, 526 | { url = "https://files.pythonhosted.org/packages/b4/04/885d3b1f650e1153cbb93a6a9782c58a972b94ea4483ae4ac5cedd5e4a09/tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd", size = 259645 }, 527 | { url = "https://files.pythonhosted.org/packages/9c/de/6b432d66e986e501586da298e28ebeefd3edc2c780f3ad73d22566034239/tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e", size = 244584 }, 528 | { url = "https://files.pythonhosted.org/packages/1c/9a/47c0449b98e6e7d1be6cbac02f93dd79003234ddc4aaab6ba07a9a7482e2/tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98", size = 98875 }, 529 | { url = "https://files.pythonhosted.org/packages/ef/60/9b9638f081c6f1261e2688bd487625cd1e660d0a85bd469e91d8db969734/tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4", size = 109418 }, 530 | { url = "https://files.pythonhosted.org/packages/04/90/2ee5f2e0362cb8a0b6499dc44f4d7d48f8fff06d28ba46e6f1eaa61a1388/tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7", size = 132708 }, 531 | { url = "https://files.pythonhosted.org/packages/c0/ec/46b4108816de6b385141f082ba99e315501ccd0a2ea23db4a100dd3990ea/tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c", size = 123582 }, 532 | { url = "https://files.pythonhosted.org/packages/a0/bd/b470466d0137b37b68d24556c38a0cc819e8febe392d5b199dcd7f578365/tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13", size = 232543 }, 533 | { url = "https://files.pythonhosted.org/packages/d9/e5/82e80ff3b751373f7cead2815bcbe2d51c895b3c990686741a8e56ec42ab/tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281", size = 241691 }, 534 | { url = "https://files.pythonhosted.org/packages/05/7e/2a110bc2713557d6a1bfb06af23dd01e7dde52b6ee7dadc589868f9abfac/tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272", size = 251170 }, 535 | { url = "https://files.pythonhosted.org/packages/64/7b/22d713946efe00e0adbcdfd6d1aa119ae03fd0b60ebed51ebb3fa9f5a2e5/tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140", size = 236530 }, 536 | { url = "https://files.pythonhosted.org/packages/38/31/3a76f67da4b0cf37b742ca76beaf819dca0ebef26d78fc794a576e08accf/tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2", size = 258666 }, 537 | { url = "https://files.pythonhosted.org/packages/07/10/5af1293da642aded87e8a988753945d0cf7e00a9452d3911dd3bb354c9e2/tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744", size = 243954 }, 538 | { url = "https://files.pythonhosted.org/packages/5b/b9/1ed31d167be802da0fc95020d04cd27b7d7065cc6fbefdd2f9186f60d7bd/tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec", size = 98724 }, 539 | { url = "https://files.pythonhosted.org/packages/c7/32/b0963458706accd9afcfeb867c0f9175a741bf7b19cd424230714d722198/tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69", size = 109383 }, 540 | { url = "https://files.pythonhosted.org/packages/6e/c2/61d3e0f47e2b74ef40a68b9e6ad5984f6241a942f7cd3bbfbdbd03861ea9/tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc", size = 14257 }, 541 | ] 542 | 543 | [[package]] 544 | name = "typing-extensions" 545 | version = "4.13.2" 546 | source = { registry = "https://pypi.org/simple" } 547 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 } 548 | wheels = [ 549 | { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 }, 550 | ] 551 | 552 | [[package]] 553 | name = "typing-inspection" 554 | version = "0.4.1" 555 | source = { registry = "https://pypi.org/simple" } 556 | dependencies = [ 557 | { name = "typing-extensions" }, 558 | ] 559 | sdist = { url = "https://files.pythonhosted.org/packages/f8/b1/0c11f5058406b3af7609f121aaa6b609744687f1d158b3c3a5bf4cc94238/typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28", size = 75726 } 560 | wheels = [ 561 | { url = "https://files.pythonhosted.org/packages/17/69/cd203477f944c353c31bade965f880aa1061fd6bf05ded0726ca845b6ff7/typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51", size = 14552 }, 562 | ] 563 | 564 | [[package]] 565 | name = "uvicorn" 566 | version = "0.34.3" 567 | source = { registry = "https://pypi.org/simple" } 568 | dependencies = [ 569 | { name = "click" }, 570 | { name = "h11" }, 571 | { name = "typing-extensions", marker = "python_full_version < '3.11'" }, 572 | ] 573 | sdist = { url = "https://files.pythonhosted.org/packages/de/ad/713be230bcda622eaa35c28f0d328c3675c371238470abdea52417f17a8e/uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a", size = 76631 } 574 | wheels = [ 575 | { url = "https://files.pythonhosted.org/packages/6d/0d/8adfeaa62945f90d19ddc461c55f4a50c258af7662d34b6a3d5d1f8646f6/uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885", size = 62431 }, 576 | ] 577 | --------------------------------------------------------------------------------