diff --git a/README.md b/README.md
index 85cf88ce..50354ba5 100644
--- a/README.md
+++ b/README.md
@@ -169,6 +169,7 @@ async with Computer(
| [**Computer (Python)**](./libs/python/computer/README.md) | Python Interface for controlling virtual machines | `pip install "cua-computer[all]"` |
| [**Computer (Typescript)**](./libs/typescript/computer/README.md) | Typescript Interface for controlling virtual machines | `npm install @trycua/computer` |
| [**Agent**](./libs/python/agent/README.md) | AI agent framework for automating tasks | `pip install "cua-agent[all]"` |
+| [**Snapshot Manager**](./libs/python/snapshot-manager/README.md) | Docker container snapshot management for agent state persistence | `pip install cua-snapshot-manager` |
| [**MCP Server**](./libs/python/mcp-server/README.md) | MCP server for using CUA with Claude Desktop | `pip install cua-mcp-server` |
| [**SOM**](./libs/python/som/README.md) | Self-of-Mark library for Agent | `pip install cua-som` |
| [**Computer Server**](./libs/python/computer-server/README.md) | Server component for Computer | `pip install cua-computer-server` |
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 00000000..4f697820
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,47 @@
+# CUA Snapshot Manager Integration TODO
+
+## Current Status: Core MVP Features Complete
+**Phase**: Named volume support completed, finalizing remaining MVP features
+**Target**: Production-ready snapshot management for CUA Agent SDK
+
+## Completed Tasks ✅
+- [x] Draft PR created in CUA repo
+- [x] ARCHITECTURE.md with concise technical overview
+- [x] DockerSnapshotProvider wired to CUA callbacks
+- [x] Integration tests with real CUA agent verified
+- [x] CI compliance validated (Black, Ruff, pytest)
+- [x] **Named Volume Support** - Tar-based backup/restore for named volumes
+- [x] **Bind Mount Detection** - User warnings for non-portable bind mounts
+- [x] **Volume Type Identification** - Full volume analysis and handling
+- [x] **Critical MyPy Fixes** - All blocking type errors resolved
+
+## Remaining MVP Requirements
+
+### Enhanced Restore Modes
+- [ ] Replace mode: stop/remove original, create replacement
+- [ ] Minimal container healthcheck after restore
+- [ ] Error handling for restore failures
+
+### Deterministic Retention Policy
+- [ ] Age-first cleanup (max_age then max_count)
+- [ ] Deterministic ordering for snapshot removal
+- [ ] Focused unit test for retention logic
+
+### Final Polish
+- [ ] CI validation in pipeline
+- [ ] Documentation review and finalization
+
+## Technical Debt (Non-blocking)
+- [ ] MyPy type annotation cleanup (1 false positive remaining)
+- [ ] CLI function return type annotations
+- [ ] Test function type hints
+
+## Test Coverage
+- **19/19 tests passing** including comprehensive volume support tests
+- Volume detection, backup, storage, restore, and error handling all verified
+- CUA integration examples working end-to-end
+
+## Next Steps
+1. Add replace restore mode with healthcheck
+2. Implement deterministic retention policy
+3. Final CI validation before team review
\ No newline at end of file
diff --git a/libs/python/snapshot-manager/ARCHITECTURE.md b/libs/python/snapshot-manager/ARCHITECTURE.md
new file mode 100644
index 00000000..6682adb6
--- /dev/null
+++ b/libs/python/snapshot-manager/ARCHITECTURE.md
@@ -0,0 +1,170 @@
+# Snapshot Manager Architecture
+
+## Overview
+The CUA Snapshot Manager provides state capture and restoration for Docker containers during AI agent execution, enabling rollback, debugging, and reliable state recovery.
+
+## System Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ CUA Agent SDK │
+├─────────────────────────────────────────────────────────────────┤
+│ ComputerAgent │ Callbacks │ Computer │ Run Lifecycle │
+└─────────────────┬───────────────────────────────────────────────┘
+ │
+ ▼
+ ┌─────────────────────┐
+ │ SnapshotCallback │ ◄─── Automatic Triggers
+ │ (CUA Integration) │ (run_start, run_end, etc.)
+ └─────────┬───────────┘
+ │
+ ▼
+ ┌─────────────────────┐
+ │ SnapshotManager │ ◄─── Manual API Calls
+ │ (Central Control) │ (CLI, Programmatic)
+ └─────────┬───────────┘
+ │
+ ┌────────┴────────┐
+ ▼ ▼
+┌─────────────────┐ ┌─────────────────┐
+│ SnapshotProvider│ │ SnapshotStorage │
+│ (Abstract) │ │ (Abstract) │
+└─────────┬───────┘ └─────────┬───────┘
+ │ │
+ ▼ ▼
+┌─────────────────┐ ┌─────────────────┐
+│ DockerSnapshot │ │ FileSystemSnap │
+│ Provider │ │ shotStorage │
+│ │ │ │
+│ • docker commit │ │ • JSON metadata │
+│ • volume backup │ │ • volume tar │
+│ • container ops │ │ • indexing │
+│ • validation │ │ • statistics │
+└─────────┬───────┘ └─────────┬───────┘
+ │ │
+ ▼ ▼
+┌─────────────────┐ ┌─────────────────┐
+│ Docker API │ │ File System │
+│ │ │ │
+│ • Images │ │ • ./snapshots/ │
+│ • Containers │ │ ├─metadata/ │
+│ • Volumes │ │ ├─volumes/ │
+│ • Commit/Run │ │ └─index.json │
+└─────────────────┘ └─────────────────┘
+```
+
+## What's Captured
+**Container State:**
+- Complete filesystem state via `docker commit` (creates new Docker image)
+- Container configuration (environment variables, working directory, user, command)
+- Mount point metadata (named volumes, bind mounts)
+- Network settings and port mappings
+- Labels and original image reference
+
+**Named Volume Data:**
+- Full volume contents backed up as tar archives
+- Volume analysis with type identification (named vs bind mounts)
+- Automatic volume restoration with data integrity verification
+- Bind mount detection with portability warnings
+
+**Metadata:**
+- Timestamp and trigger context (manual, run_start, run_end, before/after actions)
+- Human-readable descriptions and agent run IDs
+- Storage statistics and restoration tracking
+- Container relationship mapping
+- Volume backup manifest and storage locations
+
+## What's NOT Captured
+**Excluded by Design:**
+- Live running processes and their memory state
+- Temporary filesystems (`/tmp`, `/proc`, `/sys`, `/dev`)
+- Real-time network connections and sockets
+- Host-mounted bind mount contents (warned about, not portable across systems)
+- Anonymous volumes (Docker limitation, data lost on container removal)
+
+**Technical Limitations:**
+- Containers must be in valid state (running/paused/exited/created)
+- Docker daemon must be accessible and responsive
+- No cross-host container migration support
+
+## Restore Behavior
+**New Container Mode (Default):**
+- Creates fresh container from snapshot image
+- Generates unique name with timestamp suffix
+- Preserves original configuration and mounts (if available)
+- Original container remains untouched
+
+**Replace Mode (Planned):**
+- Stops and removes target container
+- Creates replacement with same name and configuration
+- Includes basic health check validation
+
+**Volume Handling:**
+- Named volumes: Full data backup as tar archives, automatic restoration with integrity verification
+- Bind mounts: Configuration preserved, warnings displayed about portability concerns
+- Anonymous volumes: Lost (Docker limitation, not backed up)
+
+## Limits & Policies
+**Storage Limits:**
+- Per-container snapshot count (default: 10)
+- Global snapshot count (default: 100)
+- Total storage size (default: 10GB)
+
+**Retention Policy (Deterministic):**
+- Primary: Age-based cleanup (default: 7 days)
+- Secondary: Count-based limit enforcement (oldest first)
+- Execution order: max_age THEN max_count
+
+**Operation Safety:**
+- Concurrent operation locks per container
+- Graceful degradation (snapshot failures don't break agent execution)
+- Atomic metadata updates (index updated only after successful snapshot)
+- Orphaned metadata cleanup on storage corruption
+
+## Integration Points
+**CUA Agent SDK:**
+- Pluggable callback handler (enable/disable without code changes)
+- Configurable trigger points (run lifecycle, action boundaries)
+- Non-blocking operation design (async throughout)
+
+**Storage Backend:**
+- JSON-based metadata with efficient indexing
+- Pluggable storage interface (filesystem, future: cloud/database)
+- Human-readable format for debugging and inspection
+
+**Docker Provider:**
+- Uses Docker's native `docker commit` for efficiency
+- Leverages Docker image layer sharing for space optimization
+- Direct integration with Docker API (no CLI shelling)
+
+## Volume Backup & Restore Logic
+
+**Volume Detection:**
+1. Analyze container mount configuration via Docker API
+2. Categorize mounts by type: named volumes vs bind mounts
+3. Generate volume analysis report with portability warnings
+
+**Backup Process:**
+1. For each named volume attached to container:
+ - Create temporary alpine container with volume mounted read-only
+ - Extract complete volume data using `docker get_archive` (tar format)
+ - Store tar archives in `./snapshots/volumes/{snapshot_id}_{volume_name}.tar`
+2. Log warnings for bind mounts (not included in backup)
+3. Record volume manifest in snapshot metadata
+
+**Restore Process:**
+1. Create or verify target volumes exist in Docker
+2. For each backed-up volume:
+ - Create temporary alpine container with volume mounted read-write
+ - Clear existing volume contents (ensuring clean restore)
+ - Extract tar data using `docker put_archive`
+ - Verify extraction success
+3. Attach restored volumes to new container with original mount paths
+
+**Storage Structure:**
+```
+./snapshots/
+├── metadata/{snapshot_id}.json # Container + volume metadata
+├── volumes/{snapshot_id}_{vol}.tar # Volume data archives
+└── index.json # Global snapshot index
+```
diff --git a/libs/python/snapshot-manager/README.md b/libs/python/snapshot-manager/README.md
new file mode 100644
index 00000000..f0cf94e0
--- /dev/null
+++ b/libs/python/snapshot-manager/README.md
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+ [](#)
+ [](#)
+ [](https://discord.com/invite/mVnXXpdE85)
+ [](https://pypi.org/project/cua-snapshot-manager/)
+
+
+
+**CUA Snapshot Manager** is a Docker container snapshot management system for the CUA Agent SDK, enabling automated state capture, storage, and restoration during AI agent execution.
+
+
+
+
+
+```bash
+pip install cua-snapshot-manager
+```
+
+## Features
+
+- **🔄 Automatic Snapshots**: Integrate with CUA Agent callbacks for hands-free operation
+- **🐳 Docker Native**: Uses `docker commit` for efficient container snapshots
+- **📦 Volume Support**: Automatic backup/restore of named volumes with bind mount warnings
+- **⚡ Configurable Triggers**: Manual, run start/end, before/after actions
+- **🧹 Smart Retention**: Automatic cleanup with configurable policies
+- **📊 Rich Metadata**: Timestamps, descriptions, agent context tracking
+- **⚙️ CLI Interface**: Command-line tools for snapshot management
+- **🔌 Pluggable Design**: Easy integration without modifying existing workflows
+
+## Quick Start
+
+### Basic Usage
+
+```python
+import asyncio
+from snapshot_manager import SnapshotManager, SnapshotTrigger, SnapshotConfig
+
+async def main():
+ config = SnapshotConfig(storage_path="./snapshots")
+ manager = SnapshotManager(config=config)
+
+ # Create snapshot
+ metadata = await manager.create_snapshot(
+ container_id="my-container",
+ trigger=SnapshotTrigger.MANUAL,
+ description="Before risky operation"
+ )
+ print(f"Created snapshot: {metadata.snapshot_id}")
+
+asyncio.run(main())
+```
+
+### CUA Agent Integration
+
+```python
+from agent import ComputerAgent
+from computer import Computer
+from snapshot_manager import SnapshotCallback, SnapshotConfig, SnapshotTrigger
+
+async def agent_with_snapshots():
+ # Configure automatic snapshots
+ config = SnapshotConfig(
+ triggers=[SnapshotTrigger.RUN_START, SnapshotTrigger.RUN_END]
+ )
+
+ snapshot_callback = SnapshotCallback(config=config)
+
+ async with Computer(os_type="linux", provider_type="docker") as computer:
+ agent = ComputerAgent(
+ model="anthropic/claude-3-5-sonnet-20241022",
+ tools=[computer],
+ callbacks=[snapshot_callback] # Enable automatic snapshots
+ )
+
+ async for result in agent.run("Take a screenshot"):
+ print(result)
+```
+
+### CLI Usage
+
+```bash
+# Create snapshot
+cua-snapshot create my-container --description "Initial state"
+
+# List snapshots
+cua-snapshot list
+
+# Restore snapshot
+cua-snapshot restore --container-name restored-container
+
+# Cleanup old snapshots
+cua-snapshot cleanup --max-age-days 7
+```
+
+## Architecture
+
+The system consists of pluggable components for maximum flexibility:
+
+- **SnapshotManager**: Central orchestrator coordinating all operations
+- **DockerSnapshotProvider**: Docker-specific implementation using `docker commit`
+- **FileSystemSnapshotStorage**: JSON-based metadata storage with indexing
+- **SnapshotCallback**: CUA Agent SDK integration for automatic snapshots
+
+See [ARCHITECTURE.md](./ARCHITECTURE.md) for detailed design decisions and technical overview.
+
+## Examples & Testing
+
+**Examples** in [examples/](./examples/):
+- [`cua_volume_example.py`](./examples/cua_volume_example.py) - CUA Agent integration with volume support
+- [`cua_callback_integration.py`](./examples/cua_callback_integration.py) - Automatic callback triggers demonstration
+
+**Tests** in [tests/](./tests/):
+- Comprehensive test suite covering snapshot manager core functionality
+- Volume support tests for detection, backup, storage, and restore
+- All 19 tests passing with full coverage of critical features
+
+## Contributing
+
+We welcome and greatly appreciate contributions to CUA Snapshot Manager! Whether you're improving documentation, adding new features, fixing bugs, or enhancing integrations, your efforts help make the system better for everyone.
+
+Join our [Discord community](https://discord.com/invite/mVnXXpdE85) to discuss ideas or get assistance.
+
+## License
+
+CUA Snapshot Manager is open-sourced under the MIT License - see the [LICENSE](LICENSE) file for details.
diff --git a/libs/python/snapshot-manager/examples/basic_usage.py b/libs/python/snapshot-manager/examples/basic_usage.py
new file mode 100644
index 00000000..3c7a9606
--- /dev/null
+++ b/libs/python/snapshot-manager/examples/basic_usage.py
@@ -0,0 +1,239 @@
+"""
+Basic usage examples for the CUA Snapshot Manager.
+
+This script demonstrates the core functionality of the snapshot system
+without requiring the full CUA Agent SDK.
+"""
+
+import asyncio
+import logging
+
+import docker
+
+from snapshot_manager import SnapshotConfig, SnapshotManager, SnapshotTrigger
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+async def basic_snapshot_operations():
+ """Demonstrate basic snapshot operations."""
+
+ print("🚀 CUA Snapshot Manager - Basic Usage Example")
+ print("=" * 50)
+
+ # Initialize the snapshot manager
+ config = SnapshotConfig(storage_path="./example_snapshots", max_snapshots_per_container=5)
+
+ manager = SnapshotManager(config=config)
+
+ # Check if Docker is available
+ try:
+ docker_client = docker.from_env()
+ docker_client.ping()
+ print("✅ Docker connection successful")
+ except Exception as e:
+ print(f"❌ Docker not available: {e}")
+ print("Please ensure Docker Desktop is running and try again.")
+ return
+
+ # List running containers
+ containers = docker_client.containers.list()
+ if not containers:
+ print("⚠️ No running containers found.")
+ print("Starting a test container for demonstration...")
+
+ # Start a simple test container
+ try:
+ container = docker_client.containers.run(
+ "alpine:latest", command="sleep 300", name="cua_snapshot_test", detach=True
+ )
+ print(f"✅ Started test container: {container.name}")
+ target_container = container.name
+ except Exception as e:
+ print(f"❌ Failed to start test container: {e}")
+ return
+ else:
+ # Use the first running container
+ target_container = containers[0].name
+ print(f"📦 Using existing container: {target_container}")
+
+ try:
+ # Validate the container
+ print(f"\n1. Validating container '{target_container}'...")
+ is_valid = await manager.provider.validate_container(target_container)
+ if is_valid:
+ print("✅ Container is valid for snapshotting")
+ else:
+ print("❌ Container is not valid for snapshotting")
+ return
+
+ # Create a manual snapshot
+ print("\n2. Creating a manual snapshot...")
+ snapshot_metadata = await manager.create_snapshot(
+ container_id=target_container,
+ trigger=SnapshotTrigger.MANUAL,
+ description="Example manual snapshot",
+ action_context="basic_usage_demo",
+ )
+ print(f"✅ Created snapshot: {snapshot_metadata.snapshot_id}")
+ print(f" Image tag: {snapshot_metadata.image_tag}")
+ print(f" Size: {snapshot_metadata.size_bytes / (1024*1024):.1f} MB")
+
+ # Create another snapshot with different trigger
+ print("\n3. Creating a run_start snapshot...")
+ snapshot2_metadata = await manager.create_snapshot(
+ container_id=target_container,
+ trigger=SnapshotTrigger.RUN_START,
+ description="Example run start snapshot",
+ run_id="demo_run_001",
+ )
+ print(f"✅ Created snapshot: {snapshot2_metadata.snapshot_id}")
+
+ # List all snapshots
+ print("\n4. Listing all snapshots...")
+ snapshots = await manager.list_snapshots()
+ print(f"Found {len(snapshots)} snapshot(s):")
+ for snapshot in snapshots:
+ size_mb = snapshot.size_bytes / (1024 * 1024) if snapshot.size_bytes else 0
+ print(f" 📸 {snapshot.snapshot_id}")
+ print(f" Container: {snapshot.container_name}")
+ print(f" Trigger: {snapshot.trigger.value}")
+ print(f" Created: {snapshot.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
+ print(f" Size: {size_mb:.1f} MB")
+ print()
+
+ # Demonstrate restoration
+ print("5. Restoring from snapshot...")
+ restored_container_id = await manager.restore_snapshot(
+ snapshot_metadata.snapshot_id,
+ )
+ print(f"✅ Restored container: {restored_container_id}")
+
+ # Get storage statistics
+ print("\n6. Storage statistics...")
+ stats = await manager.get_storage_stats()
+ print(f" Total snapshots: {stats['total_snapshots']}")
+ print(f" Total size: {stats['total_size_gb']:.2f} GB")
+ print(f" Storage path: {stats['storage_path']}")
+
+ # Cleanup demonstration
+ print("\n7. Cleaning up snapshots...")
+ deleted_count = await manager.cleanup_old_snapshots(max_age_days=0) # Delete all
+ print(f"✅ Cleaned up {deleted_count} snapshots")
+
+ except Exception as e:
+ print(f"❌ Error during demonstration: {e}")
+ logger.exception("Full error details:")
+
+ finally:
+ # Clean up test container if we created it
+ try:
+ if "container" in locals():
+ container.stop()
+ container.remove()
+ print("🧹 Cleaned up test container")
+ except Exception:
+ pass
+
+ print("\n✨ Demonstration complete!")
+
+
+async def agent_integration_example():
+ """Demonstrate how to integrate with CUA Agent SDK."""
+
+ print("\n🤖 CUA Agent SDK Integration Example")
+ print("=" * 40)
+
+ from snapshot_manager import SnapshotCallback
+
+ # Create snapshot callback
+ config = SnapshotConfig(
+ triggers=[SnapshotTrigger.RUN_START, SnapshotTrigger.RUN_END, SnapshotTrigger.AFTER_ACTION]
+ )
+
+ snapshot_callback = SnapshotCallback(config=config)
+
+ # Simulate agent run lifecycle
+ print("Simulating agent run lifecycle...")
+
+ # Simulate run start
+ run_kwargs = {"container_id": "demo_container", "run_id": "simulation_run_001"}
+
+ await snapshot_callback.on_run_start(run_kwargs, [])
+ print("📸 Run start snapshot triggered")
+
+ # Simulate computer action
+ action_item = {"action": {"type": "screenshot"}, "call_id": "action_001"}
+
+ await snapshot_callback.on_computer_call_start(action_item)
+ print("📸 Before action snapshot triggered")
+
+ await snapshot_callback.on_computer_call_end(action_item, [])
+ print("📸 After action snapshot triggered")
+
+ # Simulate run end
+ await snapshot_callback.on_run_end(run_kwargs, [], [])
+ print("📸 Run end snapshot triggered")
+
+ # Show current context
+ context = snapshot_callback.get_current_context()
+ print(f"\nAgent context: {context}")
+
+ print("✅ Agent integration simulation complete")
+
+
+async def advanced_configuration_example():
+ """Demonstrate advanced configuration options."""
+
+ print("\n⚙️ Advanced Configuration Example")
+ print("=" * 35)
+
+ # Create advanced configuration
+ advanced_config = SnapshotConfig(
+ # Triggers
+ triggers=[
+ SnapshotTrigger.RUN_START,
+ SnapshotTrigger.AFTER_ACTION,
+ SnapshotTrigger.ON_ERROR,
+ ],
+ # Storage limits
+ storage_path="./advanced_snapshots",
+ max_snapshots_per_container=3,
+ max_total_snapshots=20,
+ max_storage_size_gb=2.0,
+ # Cleanup policy
+ auto_cleanup_days=1,
+ cleanup_on_exit=True,
+ # Performance
+ compression_enabled=True,
+ parallel_operations=True,
+ # Container configuration
+ include_volumes=True,
+ exclude_paths=["/tmp", "/var/tmp", "/proc", "/sys", "/dev"],
+ # Naming
+ naming_pattern="{container_name}_{trigger}_{timestamp}",
+ )
+
+ print("Configuration created with:")
+ print(f" - Triggers: {[t.value for t in advanced_config.triggers]}")
+ print(f" - Max snapshots per container: {advanced_config.max_snapshots_per_container}")
+ print(f" - Max storage size: {advanced_config.max_storage_size_gb} GB")
+ print(f" - Auto cleanup after: {advanced_config.auto_cleanup_days} days")
+ print(f" - Naming pattern: {advanced_config.naming_pattern}")
+
+ # Create manager with advanced config
+ SnapshotManager(config=advanced_config)
+
+ print("✅ Advanced configuration applied")
+
+
+if __name__ == "__main__":
+
+ async def main():
+ await basic_snapshot_operations()
+ await agent_integration_example()
+ await advanced_configuration_example()
+
+ asyncio.run(main())
diff --git a/libs/python/snapshot-manager/examples/cua_callback_integration.py b/libs/python/snapshot-manager/examples/cua_callback_integration.py
new file mode 100644
index 00000000..e72f5b48
--- /dev/null
+++ b/libs/python/snapshot-manager/examples/cua_callback_integration.py
@@ -0,0 +1,171 @@
+"""
+CUA Callback Integration Test
+
+Tests DockerSnapshotProvider wired to CUA callbacks with automatic snapshot creation.
+Tests all snapshot triggers: RUN_START, RUN_END, BEFORE_ACTION, AFTER_ACTION.
+"""
+
+import asyncio
+import logging
+import os
+import tempfile
+import uuid
+from pathlib import Path
+
+from dotenv import load_dotenv
+from agent import ComputerAgent
+from computer import Computer
+
+from snapshot_manager import (
+ SnapshotManager,
+ SnapshotCallback,
+ SnapshotConfig,
+ SnapshotTrigger,
+)
+from snapshot_manager.providers.docker_provider import DockerSnapshotProvider
+from snapshot_manager.storage import FileSystemSnapshotStorage
+
+
+async def test_all_callback_triggers():
+ """Test all CUA callback triggers with minimal API usage."""
+ load_dotenv()
+ logging.basicConfig(level=logging.WARNING)
+
+ print("CUA Snapshot Manager - Complete Callback Integration Test")
+ print("=" * 60)
+
+ # Setup storage path
+ storage_path = Path(tempfile.gettempdir()) / "cua-callback-test"
+ print(f"Storage path: {storage_path}")
+ storage_path.mkdir(exist_ok=True)
+
+ # Test ALL triggers
+ config = SnapshotConfig(
+ triggers=[
+ SnapshotTrigger.RUN_START,
+ SnapshotTrigger.RUN_END,
+ SnapshotTrigger.BEFORE_ACTION,
+ SnapshotTrigger.AFTER_ACTION,
+ ],
+ storage_path=str(storage_path),
+ max_snapshots_per_container=20,
+ )
+
+ # Explicit DockerSnapshotProvider wiring
+ docker_provider = DockerSnapshotProvider()
+ storage = FileSystemSnapshotStorage(base_path=str(storage_path))
+ snapshot_manager = SnapshotManager(config=config, provider=docker_provider, storage=storage)
+
+ container_name = f"cua-test-{uuid.uuid4().hex[:8]}"
+
+ # Container resolver - get actual container ID from Docker
+ def get_container_id(kwargs):
+ return container_name # Use container name consistently
+
+ # Setup callback
+ snapshot_callback = SnapshotCallback(
+ snapshot_manager=snapshot_manager,
+ container_resolver=get_container_id,
+ )
+
+ print(f"Testing all triggers with container: {container_name}")
+
+ try:
+ async with Computer(
+ os_type="linux", provider_type="docker", name=container_name
+ ) as computer:
+ print("CUA Computer connected")
+
+ # Create agent with snapshot callback
+ agent = ComputerAgent(
+ model="anthropic/claude-3-7-sonnet-20250219",
+ tools=[computer],
+ callbacks=[snapshot_callback],
+ verbosity=logging.ERROR,
+ )
+
+ # Verify container can be snapshotted
+ is_valid = await docker_provider.validate_container(container_name)
+ if not is_valid:
+ print("ERROR: Container not valid for snapshotting")
+ return False
+
+ print("Running minimal task to trigger all callbacks...")
+
+ # Simple task that will trigger BEFORE_ACTION and AFTER_ACTION
+ task = "Take a screenshot"
+
+ action_count = 0
+ async for result in agent.run(task):
+ if result.get("output"):
+ for item in result["output"]:
+ if item["type"] == "computer_call":
+ action_count += 1
+
+ print(f"Task completed with {action_count} actions")
+
+ # Get all snapshots and find ones for our container
+ await asyncio.sleep(1) # Brief pause for snapshot completion
+ all_snapshots = await snapshot_manager.list_snapshots()
+
+ # Find snapshots for our container by checking container name in description/tags
+ our_snapshots = []
+ for snap in all_snapshots:
+ # Check if this snapshot is related to our container
+ if (
+ container_name in snap.container_id
+ or container_name in snap.container_name
+ or container_name in str(snap.description)
+ or container_name in str(snap.image_tag)
+ ):
+ our_snapshots.append(snap)
+
+ print(f"\nSnapshots found for {container_name}: {len(our_snapshots)}")
+
+ # Show what we found
+ for snap in our_snapshots:
+ print(f" - {snap.trigger.value} at {snap.timestamp.strftime('%H:%M:%S')}")
+
+ # Check if we got all expected triggers
+ expected = {"run_start", "run_end", "before_action", "after_action"}
+ found = {snap.trigger.value for snap in our_snapshots}
+
+ print("\nTrigger Summary:")
+ for trigger in expected:
+ status = "✅" if trigger in found else "❌"
+ print(f" {status} {trigger.upper()}")
+
+ # Success if all triggers found
+ success = expected.issubset(found)
+ if success:
+ print("\nSUCCESS: All callback triggers working!")
+ print(f"- {len(our_snapshots)} snapshots created")
+ print("- DockerSnapshotProvider correctly wired to CUA callbacks")
+ else:
+ print(f"\nMissing triggers: {expected - found}")
+
+ return success
+
+ except Exception as e:
+ print(f"ERROR: Test failed: {e}")
+ return False
+
+
+def main():
+ """Run the complete callback integration test."""
+ if not os.getenv("ANTHROPIC_API_KEY"):
+ print("ERROR: ANTHROPIC_API_KEY environment variable required")
+ return False
+
+ success = asyncio.run(test_all_callback_triggers())
+
+ if success:
+ print("\nTEST PASSED: Callback integration working correctly")
+ else:
+ print("\nTEST FAILED: Callback integration needs fixing")
+
+ return success
+
+
+if __name__ == "__main__":
+ main()
diff --git a/libs/python/snapshot-manager/examples/cua_snapshot_test.py b/libs/python/snapshot-manager/examples/cua_snapshot_test.py
new file mode 100644
index 00000000..58d0bd23
--- /dev/null
+++ b/libs/python/snapshot-manager/examples/cua_snapshot_test.py
@@ -0,0 +1,158 @@
+"""
+CUA Agent + Snapshot Manager Integration Test
+
+This test demonstrates the complete snapshot workflow:
+1. Create a CUA Computer container
+2. Take initial snapshot (before agent actions)
+3. Run agent to create a test file
+4. Take final snapshot (after agent actions)
+5. Verify file was created in the container
+6. Restore initial snapshot to new container
+7. Verify restored container doesn't have the file (proves snapshot integrity)
+"""
+
+import asyncio
+import logging
+import os
+import uuid
+
+from dotenv import load_dotenv
+from agent import ComputerAgent
+from computer import Computer
+from snapshot_manager import SnapshotManager, SnapshotTrigger, SnapshotConfig
+from snapshot_manager.models import RestoreOptions
+
+load_dotenv()
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+
+async def cua_snapshot_test():
+ """Test CUA Agent with snapshot comparison"""
+ print("\n🧪 CUA Agent + Snapshot Test")
+ print("=" * 35)
+
+ container_name = f"cua-test-{uuid.uuid4().hex[:8]}"
+ config = SnapshotConfig(storage_path="./test_snapshots")
+ snapshot_manager = SnapshotManager(config=config)
+
+ try:
+ print(f"🐳 Creating CUA Computer: {container_name}")
+
+ async with Computer(
+ os_type="linux", provider_type="docker", name=container_name
+ ) as computer:
+ print("✅ CUA Computer connected")
+
+ # Take initial snapshot
+ print("📸 Taking initial snapshot...")
+ initial_snapshot = await snapshot_manager.create_snapshot(
+ container_id=container_name,
+ trigger=SnapshotTrigger.MANUAL,
+ description="Before agent execution",
+ )
+ print(f" Initial snapshot: {initial_snapshot.snapshot_id}")
+
+ # Create agent and run task
+ agent = ComputerAgent(
+ model="anthropic/claude-3-7-sonnet-20250219",
+ tools=[computer],
+ verbosity=logging.WARNING,
+ )
+
+ print("🚀 Running task: create test.txt file")
+ prompt = "Open a terminal and create a file called 'test.txt' with content 'Hello CUA'"
+
+ action_count = 0
+ async for result in agent.run(prompt):
+ if result.get("output"):
+ for item in result["output"]:
+ if item["type"] == "computer_call":
+ action_count += 1
+ print(f"🔧 Action {action_count}: {item['action']['type']}")
+
+ print(f"✅ Task completed with {action_count} actions")
+
+ # Take final snapshot
+ print("📸 Taking final snapshot...")
+ final_snapshot = await snapshot_manager.create_snapshot(
+ container_id=container_name,
+ trigger=SnapshotTrigger.MANUAL,
+ description="After agent execution",
+ )
+ print(f" Final snapshot: {final_snapshot.snapshot_id}")
+
+ # Verify file was created
+ print("🔍 Verifying file creation...")
+ result = await asyncio.create_subprocess_exec(
+ "docker",
+ "exec",
+ container_name,
+ "cat",
+ "test.txt",
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ )
+ stdout, stderr = await result.communicate()
+
+ if stdout and b"Hello CUA" in stdout:
+ print(f"✅ File verified: '{stdout.decode().strip()}'")
+ else:
+ print(f"❌ File not found: {stderr.decode().strip() if stderr else 'No output'}")
+
+ # Test snapshot restoration
+ print("📊 Testing snapshot restoration...")
+
+ # Restore initial snapshot (should NOT have test.txt)
+ print("🔄 Restoring initial snapshot...")
+ restore_options = RestoreOptions(new_container_name=f"{container_name}-restored")
+ await snapshot_manager.restore_snapshot(
+ snapshot_id=initial_snapshot.snapshot_id, options=restore_options
+ )
+
+ # Check if test.txt exists in restored container (should be NO)
+ check_cmd = await asyncio.create_subprocess_exec(
+ "docker",
+ "exec",
+ f"{container_name}-restored",
+ "sh",
+ "-c",
+ "test -f test.txt && echo 'YES' || echo 'NO'",
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ )
+ stdout, _ = await check_cmd.communicate()
+ initial_has_file = stdout.decode().strip() == "YES"
+
+ print(
+ f" Restored container has test.txt: {'❌ NO' if not initial_has_file else '✅ YES'}"
+ )
+
+ # Clean up restored container
+ await asyncio.create_subprocess_exec("docker", "rm", "-f", f"{container_name}-restored")
+
+ if not initial_has_file:
+ print("🎉 Perfect! Snapshot restoration works correctly!")
+ else:
+ print("⚠️ Unexpected: file exists in initial snapshot")
+
+ except Exception as e:
+ print(f"❌ Test failed: {e}")
+ logger.exception("Detailed error:")
+
+
+async def main():
+ if not os.getenv("ANTHROPIC_API_KEY"):
+ print("❌ Need ANTHROPIC_API_KEY in .env file")
+ return
+
+ try:
+ await cua_snapshot_test()
+ print("\n✨ Test completed successfully!")
+ except Exception as e:
+ print(f"\n❌ Test failed: {e}")
+ logger.exception("Error details:")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/libs/python/snapshot-manager/examples/cua_volume_example.py b/libs/python/snapshot-manager/examples/cua_volume_example.py
new file mode 100644
index 00000000..3b6ff9ae
--- /dev/null
+++ b/libs/python/snapshot-manager/examples/cua_volume_example.py
@@ -0,0 +1,202 @@
+"""
+CUA Agent + Volume Snapshot Example
+
+Demonstrates:
+1. AI agent creates files in named volume
+2. Automatic snapshot captures volume data
+3. Restore preserves complete volume state
+"""
+
+import asyncio
+import logging
+import os
+import tempfile
+import uuid
+from pathlib import Path
+
+from dotenv import load_dotenv
+from agent import ComputerAgent
+from computer import Computer
+
+from snapshot_manager import SnapshotManager, SnapshotConfig, SnapshotTrigger
+from snapshot_manager.models import RestoreOptions
+from snapshot_manager.callback import SnapshotCallback
+
+
+async def cua_volume_example():
+ """CUA agent with automatic volume snapshots."""
+
+ load_dotenv()
+ # Enable callback logging to see what's happening
+ logging.basicConfig(level=logging.INFO)
+ logging.getLogger("snapshot_manager.callback").setLevel(logging.DEBUG)
+
+ print("🚀 CUA Agent + Auto Volume Snapshots")
+ print("=" * 38)
+
+ with tempfile.TemporaryDirectory() as temp_dir:
+ storage_path = Path(temp_dir) / "snapshots"
+
+ # Setup snapshot manager with automatic triggers
+ config = SnapshotConfig(
+ triggers=[SnapshotTrigger.RUN_START, SnapshotTrigger.RUN_END],
+ storage_path=str(storage_path),
+ )
+ snapshot_manager = SnapshotManager(config=config)
+ snapshot_callback = SnapshotCallback(snapshot_manager=snapshot_manager)
+
+ # Create named volume
+ import docker
+ docker_client = docker.from_env()
+ volume_name = f"agent-data-{uuid.uuid4().hex[:6]}"
+ volume = docker_client.volumes.create(volume_name)
+ print(f"📦 Created volume: {volume_name}")
+
+ container_name = f"cua-volume-example-{uuid.uuid4().hex[:6]}"
+
+ try:
+ # Start CUA Computer (like the working example)
+ async with Computer(
+ os_type="linux",
+ provider_type="docker",
+ name=container_name
+ ) as computer:
+ print("✅ CUA Computer started")
+
+ # Create a separate test container with volume to test volume snapshots
+ test_container = docker_client.containers.run(
+ image="alpine:latest",
+ name=f"{container_name}-volume-test",
+ command="sleep 300",
+ volumes=[f"{volume_name}:/data:rw"],
+ detach=True
+ )
+
+ # Add initial data to volume
+ test_container.exec_run("sh -c 'echo \"Initial volume data\" > /data/initial.txt'")
+ print("✅ Volume test container created with initial data")
+
+ # Create AI agent with snapshot callback
+ agent = ComputerAgent(
+ model="anthropic/claude-3-7-sonnet-20250219",
+ tools=[computer],
+ callbacks=[snapshot_callback], # Automatic snapshots!
+ verbosity=logging.ERROR
+ )
+
+ print("🔄 Running agent with auto-snapshots...")
+
+ # Set container for snapshots to test the volume container
+ volume_container_name = f"{container_name}-volume-test"
+ snapshot_callback.container_resolver = lambda _: volume_container_name
+
+ # Agent run will automatically trigger:
+ # 1. RUN_START snapshot (before task)
+ # 2. RUN_END snapshot (after task)
+ task = "Open terminal, type: echo 'done' > test.txt"
+
+ # Monitor for snapshots during agent run
+ print("📊 Checking snapshots before agent run...")
+ snapshots_before = await snapshot_manager.list_snapshots()
+ print(f" Snapshots before: {len(snapshots_before)}")
+
+ try:
+ action_count = 0
+ async for result in agent.run(task):
+ if result.get("output"):
+ actions = [item for item in result["output"] if item.get("type") == "computer_call"]
+ if actions:
+ action_count += len(actions)
+ print(f"🎯 Agent action {action_count}: {actions[0].get('function', 'unknown')}")
+ print(f"🎯 Agent completed {action_count} total actions")
+ except StopAsyncIteration:
+ print("🎯 Agent task completed")
+ except Exception as e:
+ print(f"⚠️ Agent error: {e}")
+
+ # Give a moment for callbacks to complete
+ print("⏱️ Waiting for callbacks to complete...")
+ await asyncio.sleep(2)
+
+ print("📊 Checking snapshots after agent run...")
+ snapshots_after = await snapshot_manager.list_snapshots()
+ print(f" Snapshots after: {len(snapshots_after)}")
+
+ for snapshot in snapshots_after:
+ print(f" - {snapshot.snapshot_id[:8]}... trigger={snapshot.trigger.value} time={snapshot.timestamp.strftime('%H:%M:%S')}")
+
+ # 3. COMPARE SNAPSHOTS
+ print("\n📊 Comparing before/after snapshots...")
+
+ snapshots = await snapshot_manager.list_snapshots()
+ print(f" Found {len(snapshots)} snapshots")
+
+ start_snapshots = [s for s in snapshots if s.trigger == SnapshotTrigger.RUN_START]
+ end_snapshots = [s for s in snapshots if s.trigger == SnapshotTrigger.RUN_END]
+
+ if not start_snapshots or not end_snapshots:
+ print(f" ⚠️ Missing snapshots: start={len(start_snapshots)}, end={len(end_snapshots)}")
+ return
+
+ start_snapshot = start_snapshots[0]
+ end_snapshot = end_snapshots[0]
+
+ print(f"📸 Start snapshot: {start_snapshot.snapshot_id[:8]}... ({start_snapshot.trigger.value})")
+ print(f"📸 End snapshot: {end_snapshot.snapshot_id[:8]}... ({end_snapshot.trigger.value})")
+
+ # 4. RESTORE START STATE & VERIFY DIFFERENCE
+ print("\n🔄 Restoring start state...")
+
+ restore_options = RestoreOptions(new_container_name=f"restored-start-{uuid.uuid4().hex[:6]}")
+ await snapshot_manager.restore_snapshot(start_snapshot.snapshot_id, options=restore_options)
+
+ # After agent runs, modify the volume container to show the difference
+ print("🔧 Modifying volume data...")
+ test_container.exec_run("sh -c 'echo \"After agent\" > /data/after.txt'")
+ print("✅ Volume data modified")
+
+ # Check volume state vs restored state
+ current_files = test_container.exec_run("ls /data/").output.decode()
+ restored_files = docker_client.containers.get(restore_options.new_container_name).exec_run("ls /data/").output.decode()
+
+ current_has_after = "after.txt" in current_files
+ restored_has_after = "after.txt" in restored_files
+
+ print(f"📁 Current volume has after file: {'✅' if current_has_after else '❌'}")
+ print(f"📁 Restored volume has after file: {'❌' if not restored_has_after else '✅'}")
+
+ # Cleanup
+ docker_client.containers.get(restore_options.new_container_name).remove(force=True)
+ test_container.stop()
+ test_container.remove()
+
+ print("\n✨ Demo Complete!")
+ print("✅ Automatic snapshots captured before/after agent run")
+ print("✅ Volume data shows clear state difference")
+ print("✅ Restore verified exact point-in-time recovery")
+
+ finally:
+ try:
+ volume.remove()
+ print(f"🧹 Cleaned up volume: {volume_name}")
+ except Exception as e:
+ print(f"⚠️ Volume cleanup: {e}")
+
+
+async def main():
+ """Run the CUA volume demo."""
+ load_dotenv()
+
+ if not os.getenv("ANTHROPIC_API_KEY"):
+ print("❌ ANTHROPIC_API_KEY required in .env file")
+ return
+
+ try:
+ await cua_volume_example()
+ print("\n✨ CUA Volume Demo Complete!")
+ except Exception as e:
+ print(f"\n❌ Demo failed: {e}")
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/libs/python/snapshot-manager/img/infra_diagram.png b/libs/python/snapshot-manager/img/infra_diagram.png
new file mode 100644
index 00000000..67728464
Binary files /dev/null and b/libs/python/snapshot-manager/img/infra_diagram.png differ
diff --git a/libs/python/snapshot-manager/pdm.lock b/libs/python/snapshot-manager/pdm.lock
new file mode 100644
index 00000000..e390265b
--- /dev/null
+++ b/libs/python/snapshot-manager/pdm.lock
@@ -0,0 +1,3666 @@
+# This file is @generated by PDM.
+# It is not intended for manual editing.
+
+[metadata]
+groups = ["default", "dev"]
+strategy = ["inherit_metadata"]
+lock_version = "4.5.0"
+content_hash = "sha256:d64738ab38fc2a6aea7a3d1d0f5820e2c86046158271b005f75fb2ed169d5246"
+
+[[metadata.targets]]
+requires_python = ">=3.12"
+
+[[package]]
+name = "accelerate"
+version = "1.10.0"
+requires_python = ">=3.9.0"
+summary = "Accelerate"
+groups = ["default"]
+dependencies = [
+ "huggingface-hub>=0.21.0",
+ "numpy<3.0.0,>=1.17",
+ "packaging>=20.0",
+ "psutil",
+ "pyyaml",
+ "safetensors>=0.4.3",
+ "torch>=2.0.0",
+]
+files = [
+ {file = "accelerate-1.10.0-py3-none-any.whl", hash = "sha256:260a72b560e100e839b517a331ec85ed495b3889d12886e79d1913071993c5a3"},
+ {file = "accelerate-1.10.0.tar.gz", hash = "sha256:8270568fda9036b5cccdc09703fef47872abccd56eb5f6d53b54ea5fb7581496"},
+]
+
+[[package]]
+name = "aiofiles"
+version = "24.1.0"
+requires_python = ">=3.8"
+summary = "File support for asyncio."
+groups = ["default"]
+files = [
+ {file = "aiofiles-24.1.0-py3-none-any.whl", hash = "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5"},
+ {file = "aiofiles-24.1.0.tar.gz", hash = "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c"},
+]
+
+[[package]]
+name = "aiohappyeyeballs"
+version = "2.6.1"
+requires_python = ">=3.9"
+summary = "Happy Eyeballs for asyncio"
+groups = ["default"]
+files = [
+ {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"},
+ {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"},
+]
+
+[[package]]
+name = "aiohttp"
+version = "3.12.15"
+requires_python = ">=3.9"
+summary = "Async http client/server framework (asyncio)"
+groups = ["default"]
+dependencies = [
+ "aiohappyeyeballs>=2.5.0",
+ "aiosignal>=1.4.0",
+ "async-timeout<6.0,>=4.0; python_version < \"3.11\"",
+ "attrs>=17.3.0",
+ "frozenlist>=1.1.1",
+ "multidict<7.0,>=4.5",
+ "propcache>=0.2.0",
+ "yarl<2.0,>=1.17.0",
+]
+files = [
+ {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7"},
+ {file = "aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444"},
+ {file = "aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545"},
+ {file = "aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea"},
+ {file = "aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3"},
+ {file = "aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1"},
+ {file = "aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34"},
+ {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315"},
+ {file = "aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd"},
+ {file = "aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64"},
+ {file = "aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51"},
+ {file = "aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0"},
+ {file = "aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84"},
+ {file = "aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2"},
+]
+
+[[package]]
+name = "aiosignal"
+version = "1.4.0"
+requires_python = ">=3.9"
+summary = "aiosignal: a list of registered asynchronous callbacks"
+groups = ["default"]
+dependencies = [
+ "frozenlist>=1.1.0",
+ "typing-extensions>=4.2; python_version < \"3.13\"",
+]
+files = [
+ {file = "aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e"},
+ {file = "aiosignal-1.4.0.tar.gz", hash = "sha256:f47eecd9468083c2029cc99945502cb7708b082c232f9aca65da147157b251c7"},
+]
+
+[[package]]
+name = "annotated-types"
+version = "0.7.0"
+requires_python = ">=3.8"
+summary = "Reusable constraint types to use with typing.Annotated"
+groups = ["default"]
+dependencies = [
+ "typing-extensions>=4.0.0; python_version < \"3.9\"",
+]
+files = [
+ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"},
+ {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"},
+]
+
+[[package]]
+name = "anyio"
+version = "4.10.0"
+requires_python = ">=3.9"
+summary = "High-level concurrency and networking framework on top of asyncio or Trio"
+groups = ["default"]
+dependencies = [
+ "exceptiongroup>=1.0.2; python_version < \"3.11\"",
+ "idna>=2.8",
+ "sniffio>=1.1",
+ "typing-extensions>=4.5; python_version < \"3.13\"",
+]
+files = [
+ {file = "anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1"},
+ {file = "anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6"},
+]
+
+[[package]]
+name = "asyncio"
+version = "4.0.0"
+requires_python = ">=3.4"
+summary = "Deprecated backport of asyncio; use the stdlib package instead"
+groups = ["default"]
+files = [
+ {file = "asyncio-4.0.0-py3-none-any.whl", hash = "sha256:c1eddb0659231837046809e68103969b2bef8b0400d59cfa6363f6b5ed8cc88b"},
+ {file = "asyncio-4.0.0.tar.gz", hash = "sha256:570cd9e50db83bc1629152d4d0b7558d6451bb1bfd5dfc2e935d96fc2f40329b"},
+]
+
+[[package]]
+name = "attrs"
+version = "25.3.0"
+requires_python = ">=3.8"
+summary = "Classes Without Boilerplate"
+groups = ["default"]
+files = [
+ {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"},
+ {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"},
+]
+
+[[package]]
+name = "audioop-lts"
+version = "0.2.2"
+requires_python = ">=3.13"
+summary = "LTS Port of Python audioop"
+groups = ["default"]
+marker = "python_version >= \"3.13\""
+files = [
+ {file = "audioop_lts-0.2.2-cp313-abi3-macosx_10_13_universal2.whl", hash = "sha256:fd3d4602dc64914d462924a08c1a9816435a2155d74f325853c1f1ac3b2d9800"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-macosx_10_13_x86_64.whl", hash = "sha256:550c114a8df0aafe9a05442a1162dfc8fec37e9af1d625ae6060fed6e756f303"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-macosx_11_0_arm64.whl", hash = "sha256:9a13dc409f2564de15dd68be65b462ba0dde01b19663720c68c1140c782d1d75"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c916108c56aa6e426ce611946f901badac950ee2ddaf302b7ed35d9958970d"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:47eba38322370347b1c47024defbd36374a211e8dd5b0dcbce7b34fdb6f8847b"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba7c3a7e5f23e215cb271516197030c32aef2e754252c4c70a50aaff7031a2c8"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:def246fe9e180626731b26e89816e79aae2276f825420a07b4a647abaa84becc"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e160bf9df356d841bb6c180eeeea1834085464626dc1b68fa4e1d59070affdc3"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4b4cd51a57b698b2d06cb9993b7ac8dfe89a3b2878e96bc7948e9f19ff51dba6"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4a53aa7c16a60a6857e6b0b165261436396ef7293f8b5c9c828a3a203147ed4a"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:3fc38008969796f0f689f1453722a0f463da1b8a6fbee11987830bfbb664f623"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_s390x.whl", hash = "sha256:15ab25dd3e620790f40e9ead897f91e79c0d3ce65fe193c8ed6c26cffdd24be7"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:03f061a1915538fd96272bac9551841859dbb2e3bf73ebe4a23ef043766f5449"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-win32.whl", hash = "sha256:3bcddaaf6cc5935a300a8387c99f7a7fbbe212a11568ec6cf6e4bc458c048636"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-win_amd64.whl", hash = "sha256:a2c2a947fae7d1062ef08c4e369e0ba2086049a5e598fda41122535557012e9e"},
+ {file = "audioop_lts-0.2.2-cp313-abi3-win_arm64.whl", hash = "sha256:5f93a5db13927a37d2d09637ccca4b2b6b48c19cd9eda7b17a2e9f77edee6a6f"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:73f80bf4cd5d2ca7814da30a120de1f9408ee0619cc75da87d0641273d202a09"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:106753a83a25ee4d6f473f2be6b0966fc1c9af7e0017192f5531a3e7463dce58"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fbdd522624141e40948ab3e8cdae6e04c748d78710e9f0f8d4dae2750831de19"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:143fad0311e8209ece30a8dbddab3b65ab419cbe8c0dde6e8828da25999be911"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dfbbc74ec68a0fd08cfec1f4b5e8cca3d3cd7de5501b01c4b5d209995033cde9"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cfcac6aa6f42397471e4943e0feb2244549db5c5d01efcd02725b96af417f3fe"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:752d76472d9804ac60f0078c79cdae8b956f293177acd2316cd1e15149aee132"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:83c381767e2cc10e93e40281a04852facc4cd9334550e0f392f72d1c0a9c5753"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c0022283e9556e0f3643b7c3c03f05063ca72b3063291834cca43234f20c60bb"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:a2d4f1513d63c795e82948e1305f31a6d530626e5f9f2605408b300ae6095093"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:c9c8e68d8b4a56fda8c025e538e639f8c5953f5073886b596c93ec9b620055e7"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:96f19de485a2925314f5020e85911fb447ff5fbef56e8c7c6927851b95533a1c"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e541c3ef484852ef36545f66209444c48b28661e864ccadb29daddb6a4b8e5f5"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-win32.whl", hash = "sha256:d5e73fa573e273e4f2e5ff96f9043858a5e9311e94ffefd88a3186a910c70917"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9191d68659eda01e448188f60364c7763a7ca6653ed3f87ebb165822153a8547"},
+ {file = "audioop_lts-0.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c174e322bb5783c099aaf87faeb240c8d210686b04bd61dfd05a8e5a83d88969"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:f9ee9b52f5f857fbaf9d605a360884f034c92c1c23021fb90b2e39b8e64bede6"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:49ee1a41738a23e98d98b937a0638357a2477bc99e61b0f768a8f654f45d9b7a"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5b00be98ccd0fc123dcfad31d50030d25fcf31488cde9e61692029cd7394733b"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6d2e0f9f7a69403e388894d4ca5ada5c47230716a03f2847cfc7bd1ecb589d6"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9b0b8a03ef474f56d1a842af1a2e01398b8f7654009823c6d9e0ecff4d5cfbf"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2b267b70747d82125f1a021506565bdc5609a2b24bcb4773c16d79d2bb260bbd"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0337d658f9b81f4cd0fdb1f47635070cc084871a3d4646d9de74fdf4e7c3d24a"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:167d3b62586faef8b6b2275c3218796b12621a60e43f7e9d5845d627b9c9b80e"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0d9385e96f9f6da847f4d571ce3cb15b5091140edf3db97276872647ce37efd7"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:48159d96962674eccdca9a3df280e864e8ac75e40a577cc97c5c42667ffabfc5"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:8fefe5868cd082db1186f2837d64cfbfa78b548ea0d0543e9b28935ccce81ce9"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:58cf54380c3884fb49fdd37dfb7a772632b6701d28edd3e2904743c5e1773602"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:088327f00488cdeed296edd9215ca159f3a5a5034741465789cad403fcf4bec0"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-win32.whl", hash = "sha256:068aa17a38b4e0e7de771c62c60bbca2455924b67a8814f3b0dee92b5820c0b3"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:a5bf613e96f49712073de86f20dbdd4014ca18efd4d34ed18c75bd808337851b"},
+ {file = "audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd"},
+ {file = "audioop_lts-0.2.2.tar.gz", hash = "sha256:64d0c62d88e67b98a1a5e71987b7aa7b5bcffc7dcee65b635823dbdd0a8dbbd0"},
+]
+
+[[package]]
+name = "backoff"
+version = "2.2.1"
+requires_python = ">=3.7,<4.0"
+summary = "Function decoration for backoff and retry"
+groups = ["default"]
+files = [
+ {file = "backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8"},
+ {file = "backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba"},
+]
+
+[[package]]
+name = "black"
+version = "25.1.0"
+requires_python = ">=3.9"
+summary = "The uncompromising code formatter."
+groups = ["dev"]
+dependencies = [
+ "click>=8.0.0",
+ "mypy-extensions>=0.4.3",
+ "packaging>=22.0",
+ "pathspec>=0.9.0",
+ "platformdirs>=2",
+ "tomli>=1.1.0; python_version < \"3.11\"",
+ "typing-extensions>=4.0.1; python_version < \"3.11\"",
+]
+files = [
+ {file = "black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b"},
+ {file = "black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc"},
+ {file = "black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f"},
+ {file = "black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba"},
+ {file = "black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f"},
+ {file = "black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3"},
+ {file = "black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171"},
+ {file = "black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18"},
+ {file = "black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717"},
+ {file = "black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666"},
+]
+
+[[package]]
+name = "brotli"
+version = "1.1.0"
+summary = "Python bindings for the Brotli compression library"
+groups = ["default"]
+files = [
+ {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:32d95b80260d79926f5fab3c41701dbb818fde1c9da590e77e571eefd14abe28"},
+ {file = "Brotli-1.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b760c65308ff1e462f65d69c12e4ae085cff3b332d894637f6273a12a482d09f"},
+ {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:316cc9b17edf613ac76b1f1f305d2a748f1b976b033b049a6ecdfd5612c70409"},
+ {file = "Brotli-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:caf9ee9a5775f3111642d33b86237b05808dafcd6268faa492250e9b78046eb2"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70051525001750221daa10907c77830bc889cb6d865cc0b813d9db7fefc21451"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7f4bf76817c14aa98cc6697ac02f3972cb8c3da93e9ef16b9c66573a68014f91"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c5516f0aed654134a2fc936325cc2e642f8a0e096d075209672eb321cff408"},
+ {file = "Brotli-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c3020404e0b5eefd7c9485ccf8393cfb75ec38ce75586e046573c9dc29967a0"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:4ed11165dd45ce798d99a136808a794a748d5dc38511303239d4e2363c0695dc"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:4093c631e96fdd49e0377a9c167bfd75b6d0bad2ace734c6eb20b348bc3ea180"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:7e4c4629ddad63006efa0ef968c8e4751c5868ff0b1c5c40f76524e894c50248"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:861bf317735688269936f755fa136a99d1ed526883859f86e41a5d43c61d8966"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:87a3044c3a35055527ac75e419dfa9f4f3667a1e887ee80360589eb8c90aabb9"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c5529b34c1c9d937168297f2c1fde7ebe9ebdd5e121297ff9c043bdb2ae3d6fb"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ca63e1890ede90b2e4454f9a65135a4d387a4585ff8282bb72964fab893f2111"},
+ {file = "Brotli-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e79e6520141d792237c70bcd7a3b122d00f2613769ae0cb61c52e89fd3443839"},
+ {file = "Brotli-1.1.0-cp312-cp312-win32.whl", hash = "sha256:5f4d5ea15c9382135076d2fb28dde923352fe02951e66935a9efaac8f10e81b0"},
+ {file = "Brotli-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:906bc3a79de8c4ae5b86d3d75a8b77e44404b0f4261714306e3ad248d8ab0951"},
+ {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8bf32b98b75c13ec7cf774164172683d6e7891088f6316e54425fde1efc276d5"},
+ {file = "Brotli-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7bc37c4d6b87fb1017ea28c9508b36bbcb0c3d18b4260fcdf08b200c74a6aee8"},
+ {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c0ef38c7a7014ffac184db9e04debe495d317cc9c6fb10071f7fefd93100a4f"},
+ {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91d7cc2a76b5567591d12c01f019dd7afce6ba8cba6571187e21e2fc418ae648"},
+ {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a93dde851926f4f2678e704fadeb39e16c35d8baebd5252c9fd94ce8ce68c4a0"},
+ {file = "Brotli-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f0db75f47be8b8abc8d9e31bc7aad0547ca26f24a54e6fd10231d623f183d089"},
+ {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6967ced6730aed543b8673008b5a391c3b1076d834ca438bbd70635c73775368"},
+ {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7eedaa5d036d9336c95915035fb57422054014ebdeb6f3b42eac809928e40d0c"},
+ {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d487f5432bf35b60ed625d7e1b448e2dc855422e87469e3f450aa5552b0eb284"},
+ {file = "Brotli-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832436e59afb93e1836081a20f324cb185836c617659b07b129141a8426973c7"},
+ {file = "Brotli-1.1.0-cp313-cp313-win32.whl", hash = "sha256:43395e90523f9c23a3d5bdf004733246fba087f2948f87ab28015f12359ca6a0"},
+ {file = "Brotli-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:9011560a466d2eb3f5a6e4929cf4a09be405c64154e12df0dd72713f6500e32b"},
+ {file = "Brotli-1.1.0.tar.gz", hash = "sha256:81de08ac11bcb85841e440c13611c00b67d3bf82698314928d0b676362546724"},
+]
+
+[[package]]
+name = "certifi"
+version = "2025.8.3"
+requires_python = ">=3.7"
+summary = "Python package for providing Mozilla's CA Bundle."
+groups = ["default"]
+files = [
+ {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"},
+ {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"},
+]
+
+[[package]]
+name = "cffi"
+version = "1.17.1"
+requires_python = ">=3.8"
+summary = "Foreign Function Interface for Python calling C code."
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "pycparser",
+]
+files = [
+ {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"},
+ {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"},
+ {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"},
+ {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"},
+ {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"},
+ {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"},
+ {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"},
+ {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"},
+ {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"},
+ {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"},
+ {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"},
+ {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"},
+ {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"},
+ {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"},
+ {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.4.3"
+requires_python = ">=3.7"
+summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+groups = ["default"]
+files = [
+ {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"},
+ {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"},
+ {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"},
+ {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"},
+ {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"},
+ {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"},
+]
+
+[[package]]
+name = "click"
+version = "8.2.1"
+requires_python = ">=3.10"
+summary = "Composable command line interface toolkit"
+groups = ["default", "dev"]
+dependencies = [
+ "colorama; platform_system == \"Windows\"",
+]
+files = [
+ {file = "click-8.2.1-py3-none-any.whl", hash = "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b"},
+ {file = "click-8.2.1.tar.gz", hash = "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202"},
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+summary = "Cross-platform colored terminal text."
+groups = ["default", "dev"]
+marker = "platform_system == \"Windows\" or sys_platform == \"win32\""
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "contourpy"
+version = "1.3.3"
+requires_python = ">=3.11"
+summary = "Python library for calculating contours of 2D quadrilateral grids"
+groups = ["default"]
+dependencies = [
+ "numpy>=1.25",
+]
+files = [
+ {file = "contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb"},
+ {file = "contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea"},
+ {file = "contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1"},
+ {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7"},
+ {file = "contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411"},
+ {file = "contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69"},
+ {file = "contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b"},
+ {file = "contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc"},
+ {file = "contourpy-1.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:177fb367556747a686509d6fef71d221a4b198a3905fe824430e5ea0fda54eb5"},
+ {file = "contourpy-1.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d002b6f00d73d69333dac9d0b8d5e84d9724ff9ef044fd63c5986e62b7c9e1b1"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:348ac1f5d4f1d66d3322420f01d42e43122f43616e0f194fc1c9f5d830c5b286"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:655456777ff65c2c548b7c454af9c6f33f16c8884f11083244b5819cc214f1b5"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:644a6853d15b2512d67881586bd03f462c7ab755db95f16f14d7e238f2852c67"},
+ {file = "contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4debd64f124ca62069f313a9cb86656ff087786016d76927ae2cf37846b006c9"},
+ {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a15459b0f4615b00bbd1e91f1b9e19b7e63aea7483d03d804186f278c0af2659"},
+ {file = "contourpy-1.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ca0fdcd73925568ca027e0b17ab07aad764be4706d0a925b89227e447d9737b7"},
+ {file = "contourpy-1.3.3-cp313-cp313-win32.whl", hash = "sha256:b20c7c9a3bf701366556e1b1984ed2d0cedf999903c51311417cf5f591d8c78d"},
+ {file = "contourpy-1.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:1cadd8b8969f060ba45ed7c1b714fe69185812ab43bd6b86a9123fe8f99c3263"},
+ {file = "contourpy-1.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:fd914713266421b7536de2bfa8181aa8c699432b6763a0ea64195ebe28bff6a9"},
+ {file = "contourpy-1.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:88df9880d507169449d434c293467418b9f6cbe82edd19284aa0409e7fdb933d"},
+ {file = "contourpy-1.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d06bb1f751ba5d417047db62bca3c8fde202b8c11fb50742ab3ab962c81e8216"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4e6b05a45525357e382909a4c1600444e2a45b4795163d3b22669285591c1ae"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab3074b48c4e2cf1a960e6bbeb7f04566bf36b1861d5c9d4d8ac04b82e38ba20"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c3d53c796f8647d6deb1abe867daeb66dcc8a97e8455efa729516b997b8ed99"},
+ {file = "contourpy-1.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50ed930df7289ff2a8d7afeb9603f8289e5704755c7e5c3bbd929c90c817164b"},
+ {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4feffb6537d64b84877da813a5c30f1422ea5739566abf0bd18065ac040e120a"},
+ {file = "contourpy-1.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2b7e9480ffe2b0cd2e787e4df64270e3a0440d9db8dc823312e2c940c167df7e"},
+ {file = "contourpy-1.3.3-cp313-cp313t-win32.whl", hash = "sha256:283edd842a01e3dcd435b1c5116798d661378d83d36d337b8dde1d16a5fc9ba3"},
+ {file = "contourpy-1.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:87acf5963fc2b34825e5b6b048f40e3635dd547f590b04d2ab317c2619ef7ae8"},
+ {file = "contourpy-1.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:3c30273eb2a55024ff31ba7d052dde990d7d8e5450f4bbb6e913558b3d6c2301"},
+ {file = "contourpy-1.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fde6c716d51c04b1c25d0b90364d0be954624a0ee9d60e23e850e8d48353d07a"},
+ {file = "contourpy-1.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:cbedb772ed74ff5be440fa8eee9bd49f64f6e3fc09436d9c7d8f1c287b121d77"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:22e9b1bd7a9b1d652cd77388465dc358dafcd2e217d35552424aa4f996f524f5"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a22738912262aa3e254e4f3cb079a95a67132fc5a063890e224393596902f5a4"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:afe5a512f31ee6bd7d0dda52ec9864c984ca3d66664444f2d72e0dc4eb832e36"},
+ {file = "contourpy-1.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f64836de09927cba6f79dcd00fdd7d5329f3fccc633468507079c829ca4db4e3"},
+ {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1fd43c3be4c8e5fd6e4f2baeae35ae18176cf2e5cced681cca908addf1cdd53b"},
+ {file = "contourpy-1.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6afc576f7b33cf00996e5c1102dc2a8f7cc89e39c0b55df93a0b78c1bd992b36"},
+ {file = "contourpy-1.3.3-cp314-cp314-win32.whl", hash = "sha256:66c8a43a4f7b8df8b71ee1840e4211a3c8d93b214b213f590e18a1beca458f7d"},
+ {file = "contourpy-1.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:cf9022ef053f2694e31d630feaacb21ea24224be1c3ad0520b13d844274614fd"},
+ {file = "contourpy-1.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:95b181891b4c71de4bb404c6621e7e2390745f887f2a026b2d99e92c17892339"},
+ {file = "contourpy-1.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:33c82d0138c0a062380332c861387650c82e4cf1747aaa6938b9b6516762e772"},
+ {file = "contourpy-1.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ea37e7b45949df430fe649e5de8351c423430046a2af20b1c1961cae3afcda77"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d304906ecc71672e9c89e87c4675dc5c2645e1f4269a5063b99b0bb29f232d13"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ca658cd1a680a5c9ea96dc61cdbae1e85c8f25849843aa799dfd3cb370ad4fbe"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ab2fd90904c503739a75b7c8c5c01160130ba67944a7b77bbf36ef8054576e7f"},
+ {file = "contourpy-1.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7301b89040075c30e5768810bc96a8e8d78085b47d8be6e4c3f5a0b4ed478a0"},
+ {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2a2a8b627d5cc6b7c41a4beff6c5ad5eb848c88255fda4a8745f7e901b32d8e4"},
+ {file = "contourpy-1.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:fd6ec6be509c787f1caf6b247f0b1ca598bef13f4ddeaa126b7658215529ba0f"},
+ {file = "contourpy-1.3.3-cp314-cp314t-win32.whl", hash = "sha256:e74a9a0f5e3fff48fb5a7f2fd2b9b70a3fe014a67522f79b7cca4c0c7e43c9ae"},
+ {file = "contourpy-1.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:13b68d6a62db8eafaebb8039218921399baf6e47bf85006fd8529f2a08ef33fc"},
+ {file = "contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b"},
+ {file = "contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880"},
+]
+
+[[package]]
+name = "cua-agent"
+version = "0.4.12"
+requires_python = ">=3.11"
+summary = "CUA (Computer Use) Agent for AI-driven computer interaction"
+groups = ["default"]
+dependencies = [
+ "aiohttp>=3.9.3",
+ "anyio>=4.4.1",
+ "asyncio",
+ "certifi>=2024.2.2",
+ "cua-computer<0.5.0,>=0.4.0",
+ "cua-core<0.2.0,>=0.1.8",
+ "httpx>=0.27.0",
+ "litellm>=1.74.12",
+ "pydantic>=2.6.4",
+ "python-dotenv>=1.0.1",
+ "rich>=13.7.1",
+ "typing-extensions>=4.12.2",
+]
+files = [
+ {file = "cua_agent-0.4.12-py3-none-any.whl", hash = "sha256:f5d356173a9ad0ddfd6342c9b5a1aa76c1ed9e5ce09504bd247e4740e0c7f5bf"},
+ {file = "cua_agent-0.4.12.tar.gz", hash = "sha256:3543ac44e6b8a8d0e817f3d7db16d66aa7101f1e2d3c2c9d10a4a9790a9c6e70"},
+]
+
+[[package]]
+name = "cua-agent"
+version = "0.4.12"
+extras = ["all"]
+requires_python = ">=3.11"
+summary = "CUA (Computer Use) Agent for AI-driven computer interaction"
+groups = ["default"]
+dependencies = [
+ "accelerate",
+ "cua-agent==0.4.12",
+ "cua-som<0.2.0,>=0.1.0",
+ "gradio>=5.23.3",
+ "mlx-vlm>=0.1.27; sys_platform == \"darwin\"",
+ "python-dotenv>=1.0.1",
+ "torch",
+ "transformers>=4.54.0",
+ "ultralytics>=8.0.0",
+ "yaspin>=3.1.0",
+]
+files = [
+ {file = "cua_agent-0.4.12-py3-none-any.whl", hash = "sha256:f5d356173a9ad0ddfd6342c9b5a1aa76c1ed9e5ce09504bd247e4740e0c7f5bf"},
+ {file = "cua_agent-0.4.12.tar.gz", hash = "sha256:3543ac44e6b8a8d0e817f3d7db16d66aa7101f1e2d3c2c9d10a4a9790a9c6e70"},
+]
+
+[[package]]
+name = "cua-computer"
+version = "0.4.3"
+requires_python = ">=3.11"
+summary = "Computer-Use Interface (CUI) framework powering Cua"
+groups = ["default"]
+dependencies = [
+ "aiohttp>=3.9.0",
+ "cua-core<0.2.0,>=0.1.0",
+ "pillow>=10.0.0",
+ "pydantic>=2.11.1",
+ "websocket-client>=1.8.0",
+ "websockets>=12.0",
+]
+files = [
+ {file = "cua_computer-0.4.3-py3-none-any.whl", hash = "sha256:f3457a2efe13e16be6dbbf0bede50528d9d205b035d5e7fe304f4a27d9fdfe5c"},
+ {file = "cua_computer-0.4.3.tar.gz", hash = "sha256:2be11191002bb92036784fac6ff4a4dda48dc2b67a32ceca4e553401e2f9f725"},
+]
+
+[[package]]
+name = "cua-core"
+version = "0.1.8"
+requires_python = ">=3.11"
+summary = "Core functionality for Cua including telemetry and shared utilities"
+groups = ["default"]
+dependencies = [
+ "httpx>=0.24.0",
+ "posthog>=3.20.0",
+ "pydantic>=2.0.0",
+]
+files = [
+ {file = "cua_core-0.1.8-py3-none-any.whl", hash = "sha256:f1f91d39cff808278b6ac1ac63ecc9eae1a9d0e929d81ee5cc697143642621c4"},
+ {file = "cua_core-0.1.8.tar.gz", hash = "sha256:27ba56d94565d8985cd2c254e278c5e4f09e5824d0bcffe6d0318d2498172322"},
+]
+
+[[package]]
+name = "cua-som"
+version = "0.1.3"
+requires_python = ">=3.10"
+summary = "Computer Vision and OCR library for detecting and analyzing UI elements"
+groups = ["default"]
+dependencies = [
+ "easyocr>=1.7.1",
+ "huggingface-hub>=0.21.4",
+ "matplotlib>=3.8.3",
+ "numpy>=1.26.4",
+ "opencv-python-headless>=4.11.0.86",
+ "pillow>=10.2.0",
+ "pydantic>=2.6.3",
+ "setuptools>=75.8.1",
+ "supervision>=0.25.1",
+ "torch>=2.2.1",
+ "torchvision>=0.17.1",
+ "typing-extensions>=4.9.0",
+ "ultralytics>=8.1.28",
+]
+files = [
+ {file = "cua_som-0.1.3-py3-none-any.whl", hash = "sha256:3ee85282dfbbd51b8ca51c51818fe5aa60bcf049f8c48dda477ba09cbb5cfa32"},
+ {file = "cua_som-0.1.3.tar.gz", hash = "sha256:28b92a7c3de46249f4953d36d398db991f79e87436866d7fe554917732a93fc7"},
+]
+
+[[package]]
+name = "cycler"
+version = "0.12.1"
+requires_python = ">=3.8"
+summary = "Composable style cycles"
+groups = ["default"]
+files = [
+ {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
+ {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
+]
+
+[[package]]
+name = "datasets"
+version = "4.0.0"
+requires_python = ">=3.9.0"
+summary = "HuggingFace community-driven open-source library of datasets"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "dill<0.3.9,>=0.3.0",
+ "filelock",
+ "fsspec[http]<=2025.3.0,>=2023.1.0",
+ "huggingface-hub>=0.24.0",
+ "multiprocess<0.70.17",
+ "numpy>=1.17",
+ "packaging",
+ "pandas",
+ "pyarrow>=15.0.0",
+ "pyyaml>=5.1",
+ "requests>=2.32.2",
+ "tqdm>=4.66.3",
+ "xxhash",
+]
+files = [
+ {file = "datasets-4.0.0-py3-none-any.whl", hash = "sha256:7ef95e62025fd122882dbce6cb904c8cd3fbc829de6669a5eb939c77d50e203d"},
+ {file = "datasets-4.0.0.tar.gz", hash = "sha256:9657e7140a9050db13443ba21cb5de185af8af944479b00e7ff1e00a61c8dbf1"},
+]
+
+[[package]]
+name = "defusedxml"
+version = "0.7.1"
+requires_python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+summary = "XML bomb protection for Python stdlib modules"
+groups = ["default"]
+files = [
+ {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
+ {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
+]
+
+[[package]]
+name = "dill"
+version = "0.3.8"
+requires_python = ">=3.8"
+summary = "serialize all of Python"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+files = [
+ {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"},
+ {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"},
+]
+
+[[package]]
+name = "distro"
+version = "1.9.0"
+requires_python = ">=3.6"
+summary = "Distro - an OS platform information API"
+groups = ["default"]
+files = [
+ {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"},
+ {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"},
+]
+
+[[package]]
+name = "docker"
+version = "7.1.0"
+requires_python = ">=3.8"
+summary = "A Python library for the Docker Engine API."
+groups = ["default"]
+dependencies = [
+ "pywin32>=304; sys_platform == \"win32\"",
+ "requests>=2.26.0",
+ "urllib3>=1.26.0",
+]
+files = [
+ {file = "docker-7.1.0-py3-none-any.whl", hash = "sha256:c96b93b7f0a746f9e77d325bcfb87422a3d8bd4f03136ae8a85b37f1898d5fc0"},
+ {file = "docker-7.1.0.tar.gz", hash = "sha256:ad8c70e6e3f8926cb8a92619b832b4ea5299e2831c14284663184e200546fa6c"},
+]
+
+[[package]]
+name = "easyocr"
+version = "1.7.2"
+summary = "End-to-End Multi-Lingual Optical Character Recognition (OCR) Solution"
+groups = ["default"]
+dependencies = [
+ "Pillow",
+ "PyYAML",
+ "Shapely",
+ "ninja",
+ "numpy",
+ "opencv-python-headless",
+ "pyclipper",
+ "python-bidi",
+ "scikit-image",
+ "scipy",
+ "torch",
+ "torchvision>=0.5",
+]
+files = [
+ {file = "easyocr-1.7.2-py3-none-any.whl", hash = "sha256:5be12f9b0e595d443c9c3d10b0542074b50f0ec2d98b141a109cd961fd1c177c"},
+]
+
+[[package]]
+name = "fastapi"
+version = "0.116.1"
+requires_python = ">=3.8"
+summary = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+groups = ["default"]
+dependencies = [
+ "pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4",
+ "starlette<0.48.0,>=0.40.0",
+ "typing-extensions>=4.8.0",
+]
+files = [
+ {file = "fastapi-0.116.1-py3-none-any.whl", hash = "sha256:c46ac7c312df840f0c9e220f7964bada936781bc4e2e6eb71f1c4d7553786565"},
+ {file = "fastapi-0.116.1.tar.gz", hash = "sha256:ed52cbf946abfd70c5a0dccb24673f0670deeb517a88b3544d03c2a6bf283143"},
+]
+
+[[package]]
+name = "ffmpy"
+version = "0.6.1"
+requires_python = ">=3.9"
+summary = "A simple Python wrapper for FFmpeg"
+groups = ["default"]
+files = [
+ {file = "ffmpy-0.6.1-py3-none-any.whl", hash = "sha256:69a37e2d7d6feb840e233d5640f3499a8b0a8657336774c86e4c52a3219222d4"},
+ {file = "ffmpy-0.6.1.tar.gz", hash = "sha256:b5830fd05f72bace05b8fb28724d54a7a63c5119d7f74ca36a75df33f749142d"},
+]
+
+[[package]]
+name = "filelock"
+version = "3.19.1"
+requires_python = ">=3.9"
+summary = "A platform independent file lock."
+groups = ["default"]
+files = [
+ {file = "filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d"},
+ {file = "filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58"},
+]
+
+[[package]]
+name = "fonttools"
+version = "4.59.1"
+requires_python = ">=3.9"
+summary = "Tools to manipulate font files"
+groups = ["default"]
+files = [
+ {file = "fonttools-4.59.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:43ab814bbba5f02a93a152ee61a04182bb5809bd2bc3609f7822e12c53ae2c91"},
+ {file = "fonttools-4.59.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4f04c3ffbfa0baafcbc550657cf83657034eb63304d27b05cff1653b448ccff6"},
+ {file = "fonttools-4.59.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d601b153e51a5a6221f0d4ec077b6bfc6ac35bfe6c19aeaa233d8990b2b71726"},
+ {file = "fonttools-4.59.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c735e385e30278c54f43a0d056736942023c9043f84ee1021eff9fd616d17693"},
+ {file = "fonttools-4.59.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1017413cdc8555dce7ee23720da490282ab7ec1cf022af90a241f33f9a49afc4"},
+ {file = "fonttools-4.59.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5c6d8d773470a5107052874341ed3c487c16ecd179976d81afed89dea5cd7406"},
+ {file = "fonttools-4.59.1-cp312-cp312-win32.whl", hash = "sha256:2a2d0d33307f6ad3a2086a95dd607c202ea8852fa9fb52af9b48811154d1428a"},
+ {file = "fonttools-4.59.1-cp312-cp312-win_amd64.whl", hash = "sha256:0b9e4fa7eaf046ed6ac470f6033d52c052481ff7a6e0a92373d14f556f298dc0"},
+ {file = "fonttools-4.59.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:89d9957b54246c6251345297dddf77a84d2c19df96af30d2de24093bbdf0528b"},
+ {file = "fonttools-4.59.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8156b11c0d5405810d216f53907bd0f8b982aa5f1e7e3127ab3be1a4062154ff"},
+ {file = "fonttools-4.59.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8387876a8011caec52d327d5e5bca705d9399ec4b17afb8b431ec50d47c17d23"},
+ {file = "fonttools-4.59.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb13823a74b3a9204a8ed76d3d6d5ec12e64cc5bc44914eb9ff1cdac04facd43"},
+ {file = "fonttools-4.59.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e1ca10da138c300f768bb68e40e5b20b6ecfbd95f91aac4cc15010b6b9d65455"},
+ {file = "fonttools-4.59.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2beb5bfc4887a3130f8625349605a3a45fe345655ce6031d1bac11017454b943"},
+ {file = "fonttools-4.59.1-cp313-cp313-win32.whl", hash = "sha256:419f16d750d78e6d704bfe97b48bba2f73b15c9418f817d0cb8a9ca87a5b94bf"},
+ {file = "fonttools-4.59.1-cp313-cp313-win_amd64.whl", hash = "sha256:c536f8a852e8d3fa71dde1ec03892aee50be59f7154b533f0bf3c1174cfd5126"},
+ {file = "fonttools-4.59.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:d5c3bfdc9663f3d4b565f9cb3b8c1efb3e178186435b45105bde7328cfddd7fe"},
+ {file = "fonttools-4.59.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ea03f1da0d722fe3c2278a05957e6550175571a4894fbf9d178ceef4a3783d2b"},
+ {file = "fonttools-4.59.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:57a3708ca6bfccb790f585fa6d8f29432ec329618a09ff94c16bcb3c55994643"},
+ {file = "fonttools-4.59.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:729367c91eb1ee84e61a733acc485065a00590618ca31c438e7dd4d600c01486"},
+ {file = "fonttools-4.59.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f8ef66ac6db450193ed150e10b3b45dde7aded10c5d279968bc63368027f62b"},
+ {file = "fonttools-4.59.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:075f745d539a998cd92cb84c339a82e53e49114ec62aaea8307c80d3ad3aef3a"},
+ {file = "fonttools-4.59.1-cp314-cp314-win32.whl", hash = "sha256:c2b0597522d4c5bb18aa5cf258746a2d4a90f25878cbe865e4d35526abd1b9fc"},
+ {file = "fonttools-4.59.1-cp314-cp314-win_amd64.whl", hash = "sha256:e9ad4ce044e3236f0814c906ccce8647046cc557539661e35211faadf76f283b"},
+ {file = "fonttools-4.59.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:652159e8214eb4856e8387ebcd6b6bd336ee258cbeb639c8be52005b122b9609"},
+ {file = "fonttools-4.59.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:43d177cd0e847ea026fedd9f099dc917da136ed8792d142298a252836390c478"},
+ {file = "fonttools-4.59.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e54437651e1440ee53a95e6ceb6ee440b67a3d348c76f45f4f48de1a5ecab019"},
+ {file = "fonttools-4.59.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6065fdec8ff44c32a483fd44abe5bcdb40dd5e2571a5034b555348f2b3a52cea"},
+ {file = "fonttools-4.59.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:42052b56d176f8b315fbc09259439c013c0cb2109df72447148aeda677599612"},
+ {file = "fonttools-4.59.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bcd52eaa5c4c593ae9f447c1d13e7e4a00ca21d755645efa660b6999425b3c88"},
+ {file = "fonttools-4.59.1-cp314-cp314t-win32.whl", hash = "sha256:02e4fdf27c550dded10fe038a5981c29f81cb9bc649ff2eaa48e80dab8998f97"},
+ {file = "fonttools-4.59.1-cp314-cp314t-win_amd64.whl", hash = "sha256:412a5fd6345872a7c249dac5bcce380393f40c1c316ac07f447bc17d51900922"},
+ {file = "fonttools-4.59.1-py3-none-any.whl", hash = "sha256:647db657073672a8330608970a984d51573557f328030566521bc03415535042"},
+ {file = "fonttools-4.59.1.tar.gz", hash = "sha256:74995b402ad09822a4c8002438e54940d9f1ecda898d2bb057729d7da983e4cb"},
+]
+
+[[package]]
+name = "frozenlist"
+version = "1.7.0"
+requires_python = ">=3.9"
+summary = "A list-like structure which implements collections.abc.MutableSequence"
+groups = ["default"]
+files = [
+ {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2"},
+ {file = "frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb"},
+ {file = "frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e"},
+ {file = "frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08"},
+ {file = "frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43"},
+ {file = "frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3"},
+ {file = "frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a"},
+ {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee80eeda5e2a4e660651370ebffd1286542b67e268aa1ac8d6dbe973120ef7ee"},
+ {file = "frozenlist-1.7.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1a81c85417b914139e3a9b995d4a1c84559afc839a93cf2cb7f15e6e5f6ed2d"},
+ {file = "frozenlist-1.7.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cbb65198a9132ebc334f237d7b0df163e4de83fb4f2bdfe46c1e654bdb0c5d43"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dab46c723eeb2c255a64f9dc05b8dd601fde66d6b19cdb82b2e09cc6ff8d8b5d"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6aeac207a759d0dedd2e40745575ae32ab30926ff4fa49b1635def65806fddee"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd8c4e58ad14b4fa7802b8be49d47993182fdd4023393899632c88fd8cd994eb"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04fb24d104f425da3540ed83cbfc31388a586a7696142004c577fa61c6298c3f"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a5c505156368e4ea6b53b5ac23c92d7edc864537ff911d2fb24c140bb175e60"},
+ {file = "frozenlist-1.7.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8bd7eb96a675f18aa5c553eb7ddc24a43c8c18f22e1f9925528128c052cdbe00"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:05579bf020096fe05a764f1f84cd104a12f78eaab68842d036772dc6d4870b4b"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:376b6222d114e97eeec13d46c486facd41d4f43bab626b7c3f6a8b4e81a5192c"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0aa7e176ebe115379b5b1c95b4096fb1c17cce0847402e227e712c27bdb5a949"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3fbba20e662b9c2130dc771e332a99eff5da078b2b2648153a40669a6d0e36ca"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f4410a0a601d349dd406b5713fec59b4cee7e71678d5b17edda7f4655a940b"},
+ {file = "frozenlist-1.7.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e2cdfaaec6a2f9327bf43c933c0319a7c429058e8537c508964a133dffee412e"},
+ {file = "frozenlist-1.7.0-cp313-cp313-win32.whl", hash = "sha256:5fc4df05a6591c7768459caba1b342d9ec23fa16195e744939ba5914596ae3e1"},
+ {file = "frozenlist-1.7.0-cp313-cp313-win_amd64.whl", hash = "sha256:52109052b9791a3e6b5d1b65f4b909703984b770694d3eb64fad124c835d7cba"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a6f86e4193bb0e235ef6ce3dde5cbabed887e0b11f516ce8a0f4d3b33078ec2d"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:82d664628865abeb32d90ae497fb93df398a69bb3434463d172b80fc25b0dd7d"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:912a7e8375a1c9a68325a902f3953191b7b292aa3c3fb0d71a216221deca460b"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9537c2777167488d539bc5de2ad262efc44388230e5118868e172dd4a552b146"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f34560fb1b4c3e30ba35fa9a13894ba39e5acfc5f60f57d8accde65f46cc5e74"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:acd03d224b0175f5a850edc104ac19040d35419eddad04e7cf2d5986d98427f1"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2038310bc582f3d6a09b3816ab01737d60bf7b1ec70f5356b09e84fb7408ab1"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8c05e4c8e5f36e5e088caa1bf78a687528f83c043706640a92cb76cd6999384"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:765bb588c86e47d0b68f23c1bee323d4b703218037765dcf3f25c838c6fecceb"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:32dc2e08c67d86d0969714dd484fd60ff08ff81d1a1e40a77dd34a387e6ebc0c"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:c0303e597eb5a5321b4de9c68e9845ac8f290d2ab3f3e2c864437d3c5a30cd65"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:a47f2abb4e29b3a8d0b530f7c3598badc6b134562b1a5caee867f7c62fee51e3"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:3d688126c242a6fabbd92e02633414d40f50bb6002fa4cf995a1d18051525657"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:4e7e9652b3d367c7bd449a727dc79d5043f48b88d0cbfd4f9f1060cf2b414104"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1a85e345b4c43db8b842cab1feb41be5cc0b10a1830e6295b69d7310f99becaf"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-win32.whl", hash = "sha256:3a14027124ddb70dfcee5148979998066897e79f89f64b13328595c4bdf77c81"},
+ {file = "frozenlist-1.7.0-cp313-cp313t-win_amd64.whl", hash = "sha256:3bf8010d71d4507775f658e9823210b7427be36625b387221642725b515dcf3e"},
+ {file = "frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e"},
+ {file = "frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f"},
+]
+
+[[package]]
+name = "fsspec"
+version = "2025.3.0"
+requires_python = ">=3.8"
+summary = "File-system specification"
+groups = ["default"]
+files = [
+ {file = "fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3"},
+ {file = "fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972"},
+]
+
+[[package]]
+name = "fsspec"
+version = "2025.3.0"
+extras = ["http"]
+requires_python = ">=3.8"
+summary = "File-system specification"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "aiohttp!=4.0.0a0,!=4.0.0a1",
+ "fsspec==2025.3.0",
+]
+files = [
+ {file = "fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3"},
+ {file = "fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972"},
+]
+
+[[package]]
+name = "gradio"
+version = "5.42.0"
+requires_python = ">=3.10"
+summary = "Python library for easily interacting with trained machine learning models"
+groups = ["default"]
+dependencies = [
+ "aiofiles<25.0,>=22.0",
+ "anyio<5.0,>=3.0",
+ "audioop-lts<1.0; python_version >= \"3.13\"",
+ "brotli>=1.1.0",
+ "fastapi<1.0,>=0.115.2",
+ "ffmpy",
+ "gradio-client==1.11.1",
+ "groovy~=0.1",
+ "httpx<1.0,>=0.24.1",
+ "huggingface-hub<1.0,>=0.33.5",
+ "jinja2<4.0",
+ "markupsafe<4.0,>=2.0",
+ "numpy<3.0,>=1.0",
+ "orjson~=3.0",
+ "packaging",
+ "pandas<3.0,>=1.0",
+ "pillow<12.0,>=8.0",
+ "pydantic<2.12,>=2.0",
+ "pydub",
+ "python-multipart>=0.0.18",
+ "pyyaml<7.0,>=5.0",
+ "ruff>=0.9.3; sys_platform != \"emscripten\"",
+ "safehttpx<0.2.0,>=0.1.6",
+ "semantic-version~=2.0",
+ "starlette<1.0,>=0.40.0; sys_platform != \"emscripten\"",
+ "tomlkit<0.14.0,>=0.12.0",
+ "typer<1.0,>=0.12; sys_platform != \"emscripten\"",
+ "typing-extensions~=4.0",
+ "urllib3~=2.0; sys_platform == \"emscripten\"",
+ "uvicorn>=0.14.0; sys_platform != \"emscripten\"",
+]
+files = [
+ {file = "gradio-5.42.0-py3-none-any.whl", hash = "sha256:3e11c076f4eaa2d545fb176551a2a72e951374882b8543aeebd45739085da5d2"},
+ {file = "gradio-5.42.0.tar.gz", hash = "sha256:74c8e18a3e6b7bd26396e8ed949521c0cde9bed68a15f589a9706f7cbcb8685f"},
+]
+
+[[package]]
+name = "gradio-client"
+version = "1.11.1"
+requires_python = ">=3.10"
+summary = "Python library for easily interacting with trained machine learning models"
+groups = ["default"]
+dependencies = [
+ "fsspec",
+ "httpx>=0.24.1",
+ "huggingface-hub>=0.19.3",
+ "packaging",
+ "typing-extensions~=4.0",
+ "websockets<16.0,>=10.0",
+]
+files = [
+ {file = "gradio_client-1.11.1-py3-none-any.whl", hash = "sha256:eb7870c2eb0c18f22613f0635a50ca805fdf6e8ddeb71d9f06b398b876372487"},
+ {file = "gradio_client-1.11.1.tar.gz", hash = "sha256:0d4885fea23b0d5ff8f4c34824839e0aa7fd1ba4f120928a7fc3a6ebde42abab"},
+]
+
+[[package]]
+name = "groovy"
+version = "0.1.2"
+requires_python = ">3.9"
+summary = "A small Python library created to help developers protect their applications from Server Side Request Forgery (SSRF) attacks."
+groups = ["default"]
+files = [
+ {file = "groovy-0.1.2-py3-none-any.whl", hash = "sha256:7f7975bab18c729a257a8b1ae9dcd70b7cafb1720481beae47719af57c35fa64"},
+ {file = "groovy-0.1.2.tar.gz", hash = "sha256:25c1dc09b3f9d7e292458aa762c6beb96ea037071bf5e917fc81fb78d2231083"},
+]
+
+[[package]]
+name = "h11"
+version = "0.16.0"
+requires_python = ">=3.8"
+summary = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+groups = ["default"]
+files = [
+ {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"},
+ {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"},
+]
+
+[[package]]
+name = "hf-xet"
+version = "1.1.7"
+requires_python = ">=3.8"
+summary = "Fast transfer of large files with the Hugging Face Hub."
+groups = ["default"]
+marker = "platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\""
+files = [
+ {file = "hf_xet-1.1.7-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:60dae4b44d520819e54e216a2505685248ec0adbdb2dd4848b17aa85a0375cde"},
+ {file = "hf_xet-1.1.7-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b109f4c11e01c057fc82004c9e51e6cdfe2cb230637644ade40c599739067b2e"},
+ {file = "hf_xet-1.1.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efaaf1a5a9fc3a501d3e71e88a6bfebc69ee3a716d0e713a931c8b8d920038f"},
+ {file = "hf_xet-1.1.7-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:751571540f9c1fbad9afcf222a5fb96daf2384bf821317b8bfb0c59d86078513"},
+ {file = "hf_xet-1.1.7-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:18b61bbae92d56ae731b92087c44efcac216071182c603fc535f8e29ec4b09b8"},
+ {file = "hf_xet-1.1.7-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:713f2bff61b252f8523739969f247aa354ad8e6d869b8281e174e2ea1bb8d604"},
+ {file = "hf_xet-1.1.7-cp37-abi3-win_amd64.whl", hash = "sha256:2e356da7d284479ae0f1dea3cf5a2f74fdf925d6dca84ac4341930d892c7cb34"},
+ {file = "hf_xet-1.1.7.tar.gz", hash = "sha256:20cec8db4561338824a3b5f8c19774055b04a8df7fff0cb1ff2cb1a0c1607b80"},
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.9"
+requires_python = ">=3.8"
+summary = "A minimal low-level HTTP client."
+groups = ["default"]
+dependencies = [
+ "certifi",
+ "h11>=0.16",
+]
+files = [
+ {file = "httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55"},
+ {file = "httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8"},
+]
+
+[[package]]
+name = "httpx"
+version = "0.28.1"
+requires_python = ">=3.8"
+summary = "The next generation HTTP client."
+groups = ["default"]
+dependencies = [
+ "anyio",
+ "certifi",
+ "httpcore==1.*",
+ "idna",
+]
+files = [
+ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
+ {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
+]
+
+[[package]]
+name = "huggingface-hub"
+version = "0.34.4"
+requires_python = ">=3.8.0"
+summary = "Client library to download and publish models, datasets and other repos on the huggingface.co hub"
+groups = ["default"]
+dependencies = [
+ "filelock",
+ "fsspec>=2023.5.0",
+ "hf-xet<2.0.0,>=1.1.3; platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"arm64\" or platform_machine == \"aarch64\"",
+ "packaging>=20.9",
+ "pyyaml>=5.1",
+ "requests",
+ "tqdm>=4.42.1",
+ "typing-extensions>=3.7.4.3",
+]
+files = [
+ {file = "huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a"},
+ {file = "huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c"},
+]
+
+[[package]]
+name = "idna"
+version = "3.10"
+requires_python = ">=3.6"
+summary = "Internationalized Domain Names in Applications (IDNA)"
+groups = ["default"]
+files = [
+ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"},
+ {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"},
+]
+
+[[package]]
+name = "imageio"
+version = "2.37.0"
+requires_python = ">=3.9"
+summary = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats."
+groups = ["default"]
+dependencies = [
+ "numpy",
+ "pillow>=8.3.2",
+]
+files = [
+ {file = "imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed"},
+ {file = "imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996"},
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "8.7.0"
+requires_python = ">=3.9"
+summary = "Read metadata from Python packages"
+groups = ["default"]
+dependencies = [
+ "typing-extensions>=3.6.4; python_version < \"3.8\"",
+ "zipp>=3.20",
+]
+files = [
+ {file = "importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd"},
+ {file = "importlib_metadata-8.7.0.tar.gz", hash = "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000"},
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.1.0"
+requires_python = ">=3.8"
+summary = "brain-dead simple config-ini parsing"
+groups = ["dev"]
+files = [
+ {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"},
+ {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"},
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.6"
+requires_python = ">=3.7"
+summary = "A very fast and expressive template engine."
+groups = ["default"]
+dependencies = [
+ "MarkupSafe>=2.0",
+]
+files = [
+ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
+ {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
+]
+
+[[package]]
+name = "jiter"
+version = "0.10.0"
+requires_python = ">=3.9"
+summary = "Fast iterable JSON parser."
+groups = ["default"]
+files = [
+ {file = "jiter-0.10.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1e274728e4a5345a6dde2d343c8da018b9d4bd4350f5a472fa91f66fda44911b"},
+ {file = "jiter-0.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7202ae396446c988cb2a5feb33a543ab2165b786ac97f53b59aafb803fef0744"},
+ {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23ba7722d6748b6920ed02a8f1726fb4b33e0fd2f3f621816a8b486c66410ab2"},
+ {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:371eab43c0a288537d30e1f0b193bc4eca90439fc08a022dd83e5e07500ed026"},
+ {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6c675736059020365cebc845a820214765162728b51ab1e03a1b7b3abb70f74c"},
+ {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c5867d40ab716e4684858e4887489685968a47e3ba222e44cde6e4a2154f959"},
+ {file = "jiter-0.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395bb9a26111b60141757d874d27fdea01b17e8fac958b91c20128ba8f4acc8a"},
+ {file = "jiter-0.10.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6842184aed5cdb07e0c7e20e5bdcfafe33515ee1741a6835353bb45fe5d1bd95"},
+ {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:62755d1bcea9876770d4df713d82606c8c1a3dca88ff39046b85a048566d56ea"},
+ {file = "jiter-0.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533efbce2cacec78d5ba73a41756beff8431dfa1694b6346ce7af3a12c42202b"},
+ {file = "jiter-0.10.0-cp312-cp312-win32.whl", hash = "sha256:8be921f0cadd245e981b964dfbcd6fd4bc4e254cdc069490416dd7a2632ecc01"},
+ {file = "jiter-0.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7c7d785ae9dda68c2678532a5a1581347e9c15362ae9f6e68f3fdbfb64f2e49"},
+ {file = "jiter-0.10.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e0588107ec8e11b6f5ef0e0d656fb2803ac6cf94a96b2b9fc675c0e3ab5e8644"},
+ {file = "jiter-0.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cafc4628b616dc32530c20ee53d71589816cf385dd9449633e910d596b1f5c8a"},
+ {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:520ef6d981172693786a49ff5b09eda72a42e539f14788124a07530f785c3ad6"},
+ {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:554dedfd05937f8fc45d17ebdf298fe7e0c77458232bcb73d9fbbf4c6455f5b3"},
+ {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc299da7789deacf95f64052d97f75c16d4fc8c4c214a22bf8d859a4288a1c2"},
+ {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5161e201172de298a8a1baad95eb85db4fb90e902353b1f6a41d64ea64644e25"},
+ {file = "jiter-0.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e2227db6ba93cb3e2bf67c87e594adde0609f146344e8207e8730364db27041"},
+ {file = "jiter-0.10.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:15acb267ea5e2c64515574b06a8bf393fbfee6a50eb1673614aa45f4613c0cca"},
+ {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:901b92f2e2947dc6dfcb52fd624453862e16665ea909a08398dde19c0731b7f4"},
+ {file = "jiter-0.10.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d0cb9a125d5a3ec971a094a845eadde2db0de85b33c9f13eb94a0c63d463879e"},
+ {file = "jiter-0.10.0-cp313-cp313-win32.whl", hash = "sha256:48a403277ad1ee208fb930bdf91745e4d2d6e47253eedc96e2559d1e6527006d"},
+ {file = "jiter-0.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:75f9eb72ecb640619c29bf714e78c9c46c9c4eaafd644bf78577ede459f330d4"},
+ {file = "jiter-0.10.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:28ed2a4c05a1f32ef0e1d24c2611330219fed727dae01789f4a335617634b1ca"},
+ {file = "jiter-0.10.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14a4c418b1ec86a195f1ca69da8b23e8926c752b685af665ce30777233dfe070"},
+ {file = "jiter-0.10.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d7bfed2fe1fe0e4dda6ef682cee888ba444b21e7a6553e03252e4feb6cf0adca"},
+ {file = "jiter-0.10.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:5e9251a5e83fab8d87799d3e1a46cb4b7f2919b895c6f4483629ed2446f66522"},
+ {file = "jiter-0.10.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:023aa0204126fe5b87ccbcd75c8a0d0261b9abdbbf46d55e7ae9f8e22424eeb8"},
+ {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c189c4f1779c05f75fc17c0c1267594ed918996a231593a21a5ca5438445216"},
+ {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15720084d90d1098ca0229352607cd68256c76991f6b374af96f36920eae13c4"},
+ {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4f2fb68e5f1cfee30e2b2a09549a00683e0fde4c6a2ab88c94072fc33cb7426"},
+ {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce541693355fc6da424c08b7edf39a2895f58d6ea17d92cc2b168d20907dee12"},
+ {file = "jiter-0.10.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31c50c40272e189d50006ad5c73883caabb73d4e9748a688b216e85a9a9ca3b9"},
+ {file = "jiter-0.10.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fa3402a2ff9815960e0372a47b75c76979d74402448509ccd49a275fa983ef8a"},
+ {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:1956f934dca32d7bb647ea21d06d93ca40868b505c228556d3373cbd255ce853"},
+ {file = "jiter-0.10.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:fcedb049bdfc555e261d6f65a6abe1d5ad68825b7202ccb9692636c70fcced86"},
+ {file = "jiter-0.10.0-cp314-cp314-win32.whl", hash = "sha256:ac509f7eccca54b2a29daeb516fb95b6f0bd0d0d8084efaf8ed5dfc7b9f0b357"},
+ {file = "jiter-0.10.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5ed975b83a2b8639356151cef5c0d597c68376fc4922b45d0eb384ac058cfa00"},
+ {file = "jiter-0.10.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa96f2abba33dc77f79b4cf791840230375f9534e5fac927ccceb58c5e604a5"},
+ {file = "jiter-0.10.0.tar.gz", hash = "sha256:07a7142c38aacc85194391108dc91b5b57093c978a9932bd86a36862759d9500"},
+]
+
+[[package]]
+name = "jsonschema"
+version = "4.25.0"
+requires_python = ">=3.9"
+summary = "An implementation of JSON Schema validation for Python"
+groups = ["default"]
+dependencies = [
+ "attrs>=22.2.0",
+ "jsonschema-specifications>=2023.03.6",
+ "referencing>=0.28.4",
+ "rpds-py>=0.7.1",
+]
+files = [
+ {file = "jsonschema-4.25.0-py3-none-any.whl", hash = "sha256:24c2e8da302de79c8b9382fee3e76b355e44d2a4364bb207159ce10b517bd716"},
+ {file = "jsonschema-4.25.0.tar.gz", hash = "sha256:e63acf5c11762c0e6672ffb61482bdf57f0876684d8d249c0fe2d730d48bc55f"},
+]
+
+[[package]]
+name = "jsonschema-specifications"
+version = "2025.4.1"
+requires_python = ">=3.9"
+summary = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry"
+groups = ["default"]
+dependencies = [
+ "referencing>=0.31.0",
+]
+files = [
+ {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"},
+ {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"},
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.9"
+requires_python = ">=3.10"
+summary = "A fast implementation of the Cassowary constraint solver"
+groups = ["default"]
+files = [
+ {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54"},
+ {file = "kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d"},
+ {file = "kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07"},
+ {file = "kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7"},
+ {file = "kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891"},
+ {file = "kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32"},
+ {file = "kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d"},
+]
+
+[[package]]
+name = "lazy-loader"
+version = "0.4"
+requires_python = ">=3.7"
+summary = "Makes it easy to load subpackages and functions on demand."
+groups = ["default"]
+dependencies = [
+ "importlib-metadata; python_version < \"3.8\"",
+ "packaging",
+]
+files = [
+ {file = "lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc"},
+ {file = "lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1"},
+]
+
+[[package]]
+name = "litellm"
+version = "1.75.7"
+requires_python = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
+summary = "Library to easily interface with LLM API providers"
+groups = ["default"]
+dependencies = [
+ "aiohttp>=3.10",
+ "click",
+ "httpx>=0.23.0",
+ "importlib-metadata>=6.8.0",
+ "jinja2<4.0.0,>=3.1.2",
+ "jsonschema<5.0.0,>=4.22.0",
+ "openai>=1.99.5",
+ "pydantic<3.0.0,>=2.5.0",
+ "python-dotenv>=0.2.0",
+ "tiktoken>=0.7.0",
+ "tokenizers",
+]
+files = [
+ {file = "litellm-1.75.7-py3-none-any.whl", hash = "sha256:ac42ac0986d43923ce712005e307079d99eebb3901c15399644eba7c79978727"},
+ {file = "litellm-1.75.7.tar.gz", hash = "sha256:147c2027b0c57c93252d5e479294b480ddf3389cf886e0b94658a58b2b36e767"},
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "4.0.0"
+requires_python = ">=3.10"
+summary = "Python port of markdown-it. Markdown parsing, done right!"
+groups = ["default"]
+dependencies = [
+ "mdurl~=0.1",
+]
+files = [
+ {file = "markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147"},
+ {file = "markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3"},
+]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+requires_python = ">=3.9"
+summary = "Safely add untrusted strings to HTML/XML markup."
+groups = ["default"]
+files = [
+ {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
+ {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
+ {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
+ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.10.5"
+requires_python = ">=3.10"
+summary = "Python plotting package"
+groups = ["default"]
+dependencies = [
+ "contourpy>=1.0.1",
+ "cycler>=0.10",
+ "fonttools>=4.22.0",
+ "kiwisolver>=1.3.1",
+ "numpy>=1.23",
+ "packaging>=20.0",
+ "pillow>=8",
+ "pyparsing>=2.3.1",
+ "python-dateutil>=2.7",
+]
+files = [
+ {file = "matplotlib-3.10.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:00b6feadc28a08bd3c65b2894f56cf3c94fc8f7adcbc6ab4516ae1e8ed8f62e2"},
+ {file = "matplotlib-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee98a5c5344dc7f48dc261b6ba5d9900c008fc12beb3fa6ebda81273602cc389"},
+ {file = "matplotlib-3.10.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a17e57e33de901d221a07af32c08870ed4528db0b6059dce7d7e65c1122d4bea"},
+ {file = "matplotlib-3.10.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97b9d6443419085950ee4a5b1ee08c363e5c43d7176e55513479e53669e88468"},
+ {file = "matplotlib-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ceefe5d40807d29a66ae916c6a3915d60ef9f028ce1927b84e727be91d884369"},
+ {file = "matplotlib-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:c04cba0f93d40e45b3c187c6c52c17f24535b27d545f757a2fffebc06c12b98b"},
+ {file = "matplotlib-3.10.5-cp312-cp312-win_arm64.whl", hash = "sha256:a41bcb6e2c8e79dc99c5511ae6f7787d2fb52efd3d805fff06d5d4f667db16b2"},
+ {file = "matplotlib-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:354204db3f7d5caaa10e5de74549ef6a05a4550fdd1c8f831ab9bca81efd39ed"},
+ {file = "matplotlib-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b072aac0c3ad563a2b3318124756cb6112157017f7431626600ecbe890df57a1"},
+ {file = "matplotlib-3.10.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d52fd5b684d541b5a51fb276b2b97b010c75bee9aa392f96b4a07aeb491e33c7"},
+ {file = "matplotlib-3.10.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee7a09ae2f4676276f5a65bd9f2bd91b4f9fbaedf49f40267ce3f9b448de501f"},
+ {file = "matplotlib-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ba6c3c9c067b83481d647af88b4e441d532acdb5ef22178a14935b0b881188f4"},
+ {file = "matplotlib-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:07442d2692c9bd1cceaa4afb4bbe5b57b98a7599de4dabfcca92d3eea70f9ebe"},
+ {file = "matplotlib-3.10.5-cp313-cp313-win_arm64.whl", hash = "sha256:48fe6d47380b68a37ccfcc94f009530e84d41f71f5dae7eda7c4a5a84aa0a674"},
+ {file = "matplotlib-3.10.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b80eb8621331449fc519541a7461987f10afa4f9cfd91afcd2276ebe19bd56c"},
+ {file = "matplotlib-3.10.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:47a388908e469d6ca2a6015858fa924e0e8a2345a37125948d8e93a91c47933e"},
+ {file = "matplotlib-3.10.5-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8b6b49167d208358983ce26e43aa4196073b4702858670f2eb111f9a10652b4b"},
+ {file = "matplotlib-3.10.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a8da0453a7fd8e3da114234ba70c5ba9ef0e98f190309ddfde0f089accd46ea"},
+ {file = "matplotlib-3.10.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:52c6573dfcb7726a9907b482cd5b92e6b5499b284ffacb04ffbfe06b3e568124"},
+ {file = "matplotlib-3.10.5-cp313-cp313t-win_amd64.whl", hash = "sha256:a23193db2e9d64ece69cac0c8231849db7dd77ce59c7b89948cf9d0ce655a3ce"},
+ {file = "matplotlib-3.10.5-cp313-cp313t-win_arm64.whl", hash = "sha256:56da3b102cf6da2776fef3e71cd96fcf22103a13594a18ac9a9b31314e0be154"},
+ {file = "matplotlib-3.10.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:96ef8f5a3696f20f55597ffa91c28e2e73088df25c555f8d4754931515512715"},
+ {file = "matplotlib-3.10.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:77fab633e94b9da60512d4fa0213daeb76d5a7b05156840c4fd0399b4b818837"},
+ {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27f52634315e96b1debbfdc5c416592edcd9c4221bc2f520fd39c33db5d9f202"},
+ {file = "matplotlib-3.10.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:525f6e28c485c769d1f07935b660c864de41c37fd716bfa64158ea646f7084bb"},
+ {file = "matplotlib-3.10.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1f5f3ec4c191253c5f2b7c07096a142c6a1c024d9f738247bfc8e3f9643fc975"},
+ {file = "matplotlib-3.10.5-cp314-cp314-win_amd64.whl", hash = "sha256:707f9c292c4cd4716f19ab8a1f93f26598222cd931e0cd98fbbb1c5994bf7667"},
+ {file = "matplotlib-3.10.5-cp314-cp314-win_arm64.whl", hash = "sha256:21a95b9bf408178d372814de7baacd61c712a62cae560b5e6f35d791776f6516"},
+ {file = "matplotlib-3.10.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a6b310f95e1102a8c7c817ef17b60ee5d1851b8c71b63d9286b66b177963039e"},
+ {file = "matplotlib-3.10.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:94986a242747a0605cb3ff1cb98691c736f28a59f8ffe5175acaeb7397c49a5a"},
+ {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ff10ea43288f0c8bab608a305dc6c918cc729d429c31dcbbecde3b9f4d5b569"},
+ {file = "matplotlib-3.10.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f6adb644c9d040ffb0d3434e440490a66cf73dbfa118a6f79cd7568431f7a012"},
+ {file = "matplotlib-3.10.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4fa40a8f98428f789a9dcacd625f59b7bc4e3ef6c8c7c80187a7a709475cf592"},
+ {file = "matplotlib-3.10.5-cp314-cp314t-win_amd64.whl", hash = "sha256:95672a5d628b44207aab91ec20bf59c26da99de12b88f7e0b1fb0a84a86ff959"},
+ {file = "matplotlib-3.10.5-cp314-cp314t-win_arm64.whl", hash = "sha256:2efaf97d72629e74252e0b5e3c46813e9eeaa94e011ecf8084a971a31a97f40b"},
+ {file = "matplotlib-3.10.5.tar.gz", hash = "sha256:352ed6ccfb7998a00881692f38b4ca083c691d3e275b4145423704c34c909076"},
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+requires_python = ">=3.7"
+summary = "Markdown URL utilities"
+groups = ["default"]
+files = [
+ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+ {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+
+[[package]]
+name = "mlx"
+version = "0.28.0"
+requires_python = ">=3.9"
+summary = "A framework for machine learning on Apple silicon."
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "mlx-metal==0.28.0; platform_system == \"Darwin\"",
+]
+files = [
+ {file = "mlx-0.28.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:97866d5e454e8f2d7bc42aadcbfd7565d40f4755564785e4fb964812fbad604b"},
+ {file = "mlx-0.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5204ebf399439e5da374295f6c1b6961355824604eed7026c18edfe4c83e9243"},
+ {file = "mlx-0.28.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:34776bd3fe97bca7c6c76d77f6104e0d6b05b3626bb3cf9ed48d3a9bbd46c180"},
+ {file = "mlx-0.28.0-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:ead9a6c13b704239b5ca50d68f5effc505c8f15a6017f35d8b4d0e25832e29dd"},
+ {file = "mlx-0.28.0-cp313-cp313-macosx_13_0_arm64.whl", hash = "sha256:78c88e5cc4188f538935b23803e10eaf084caa8bfeaa2a6de983038ecee3fd78"},
+ {file = "mlx-0.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:0b7a57a584ea5e807ec0a17c4eb179a71e01eeff9f25dff6950abad1e30443c2"},
+ {file = "mlx-0.28.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:a7cdcbd3faff45c18e9f51f95e9aa9410c71bbb4d5d86878a97eb996a0467505"},
+ {file = "mlx-0.28.0-cp313-cp313-manylinux_2_35_x86_64.whl", hash = "sha256:91f76bea8a192b423fa2b458a4c293c6b36e2b4b7f13a15eea94f0dc6ae03f13"},
+]
+
+[[package]]
+name = "mlx-lm"
+version = "0.26.3"
+requires_python = ">=3.8"
+summary = "LLMs with MLX and the Hugging Face Hub"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "jinja2",
+ "mlx>=0.26.0",
+ "numpy",
+ "protobuf",
+ "pyyaml",
+ "transformers>=4.39.3",
+]
+files = [
+ {file = "mlx_lm-0.26.3-py3-none-any.whl", hash = "sha256:c6a9e44bd707822bc165ce638723ab11252e8334b7b3bf79c7d399c8c3d6d48e"},
+ {file = "mlx_lm-0.26.3.tar.gz", hash = "sha256:06cd74ee3eea920335c528e68feb854eede45fe4e5f149b464ac100c1dbeaded"},
+]
+
+[[package]]
+name = "mlx-metal"
+version = "0.28.0"
+requires_python = ">=3.9"
+summary = "A framework for machine learning on Apple silicon."
+groups = ["default"]
+marker = "platform_system == \"Darwin\" and sys_platform == \"darwin\""
+files = [
+ {file = "mlx_metal-0.28.0-py3-none-macosx_13_0_arm64.whl", hash = "sha256:ce08d40f1fad4f0b3bc87bfff5d603c7fe7dd141c082ba9ce9328b41e8f8d46b"},
+ {file = "mlx_metal-0.28.0-py3-none-macosx_14_0_arm64.whl", hash = "sha256:424142ab843e2ac0b14edb58cf88d96723823c565291f46ddeeaa072abcc991e"},
+ {file = "mlx_metal-0.28.0-py3-none-macosx_15_0_arm64.whl", hash = "sha256:214ece3781d44f57eb9686561594b28915ec5568df4a5a73da59c66880b204ed"},
+]
+
+[[package]]
+name = "mlx-vlm"
+version = "0.3.2"
+requires_python = ">=3.8"
+summary = "MLX-VLM is a package for inference and fine-tuning of Vision Language Models (VLMs) and Omni Models (VLMs with audio and video support) on your Mac using MLX."
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "Pillow>=10.3.0",
+ "datasets>=2.19.1",
+ "fastapi>=0.95.1",
+ "mlx-lm>=0.23.0",
+ "mlx>=0.26.0",
+ "numpy",
+ "opencv-python>=4.12.0.88",
+ "requests>=2.31.0",
+ "scipy>=1.15.3",
+ "soundfile>=0.13.1",
+ "tqdm>=4.66.2",
+ "transformers>=4.53.0",
+ "uvicorn",
+]
+files = [
+ {file = "mlx_vlm-0.3.2-py3-none-any.whl", hash = "sha256:281fd799556b11234539c9454ed1f411c6ec6f34dc25d153adc6d057a464d352"},
+ {file = "mlx_vlm-0.3.2.tar.gz", hash = "sha256:bbb251a514d995849c9f67acc491467767704c393c9399ada11602cd88facf36"},
+]
+
+[[package]]
+name = "mpmath"
+version = "1.3.0"
+summary = "Python library for arbitrary-precision floating-point arithmetic"
+groups = ["default"]
+files = [
+ {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"},
+ {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"},
+]
+
+[[package]]
+name = "multidict"
+version = "6.6.4"
+requires_python = ">=3.9"
+summary = "multidict implementation"
+groups = ["default"]
+dependencies = [
+ "typing-extensions>=4.1.0; python_version < \"3.11\"",
+]
+files = [
+ {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8"},
+ {file = "multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3"},
+ {file = "multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c"},
+ {file = "multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802"},
+ {file = "multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24"},
+ {file = "multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793"},
+ {file = "multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e"},
+ {file = "multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364"},
+ {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e"},
+ {file = "multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657"},
+ {file = "multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a"},
+ {file = "multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812"},
+ {file = "multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a"},
+ {file = "multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69"},
+ {file = "multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf"},
+ {file = "multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605"},
+ {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb"},
+ {file = "multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e"},
+ {file = "multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45"},
+ {file = "multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0"},
+ {file = "multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92"},
+ {file = "multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e"},
+ {file = "multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4"},
+ {file = "multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad"},
+ {file = "multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c"},
+ {file = "multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd"},
+]
+
+[[package]]
+name = "multiprocess"
+version = "0.70.16"
+requires_python = ">=3.8"
+summary = "better multiprocessing and multithreading in Python"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "dill>=0.3.8",
+]
+files = [
+ {file = "multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e"},
+ {file = "multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1"},
+]
+
+[[package]]
+name = "mypy"
+version = "1.17.1"
+requires_python = ">=3.9"
+summary = "Optional static typing for Python"
+groups = ["dev"]
+dependencies = [
+ "mypy-extensions>=1.0.0",
+ "pathspec>=0.9.0",
+ "tomli>=1.1.0; python_version < \"3.11\"",
+ "typing-extensions>=4.6.0",
+]
+files = [
+ {file = "mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb"},
+ {file = "mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403"},
+ {file = "mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056"},
+ {file = "mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341"},
+ {file = "mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb"},
+ {file = "mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19"},
+ {file = "mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7"},
+ {file = "mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81"},
+ {file = "mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6"},
+ {file = "mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849"},
+ {file = "mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14"},
+ {file = "mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a"},
+ {file = "mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733"},
+ {file = "mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd"},
+ {file = "mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0"},
+ {file = "mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a"},
+ {file = "mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91"},
+ {file = "mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed"},
+ {file = "mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9"},
+ {file = "mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01"},
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.1.0"
+requires_python = ">=3.8"
+summary = "Type system extensions for programs checked with the mypy type checker."
+groups = ["dev"]
+files = [
+ {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
+ {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
+]
+
+[[package]]
+name = "networkx"
+version = "3.5"
+requires_python = ">=3.11"
+summary = "Python package for creating and manipulating graphs and networks"
+groups = ["default"]
+files = [
+ {file = "networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec"},
+ {file = "networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037"},
+]
+
+[[package]]
+name = "ninja"
+version = "1.13.0"
+requires_python = ">=3.8"
+summary = "Ninja is a small build system with a focus on speed"
+groups = ["default"]
+files = [
+ {file = "ninja-1.13.0-py3-none-macosx_10_9_universal2.whl", hash = "sha256:fa2a8bfc62e31b08f83127d1613d10821775a0eb334197154c4d6067b7068ff1"},
+ {file = "ninja-1.13.0-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3d00c692fb717fd511abeb44b8c5d00340c36938c12d6538ba989fe764e79630"},
+ {file = "ninja-1.13.0-py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:be7f478ff9f96a128b599a964fc60a6a87b9fa332ee1bd44fa243ac88d50291c"},
+ {file = "ninja-1.13.0-py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:60056592cf495e9a6a4bea3cd178903056ecb0943e4de45a2ea825edb6dc8d3e"},
+ {file = "ninja-1.13.0-py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1c97223cdda0417f414bf864cfb73b72d8777e57ebb279c5f6de368de0062988"},
+ {file = "ninja-1.13.0-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fb46acf6b93b8dd0322adc3a4945452a4e774b75b91293bafcc7b7f8e6517dfa"},
+ {file = "ninja-1.13.0-py3-none-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4be9c1b082d244b1ad7ef41eb8ab088aae8c109a9f3f0b3e56a252d3e00f42c1"},
+ {file = "ninja-1.13.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:6739d3352073341ad284246f81339a384eec091d9851a886dfa5b00a6d48b3e2"},
+ {file = "ninja-1.13.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:11be2d22027bde06f14c343f01d31446747dbb51e72d00decca2eb99be911e2f"},
+ {file = "ninja-1.13.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:aa45b4037b313c2f698bc13306239b8b93b4680eb47e287773156ac9e9304714"},
+ {file = "ninja-1.13.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5f8e1e8a1a30835eeb51db05cf5a67151ad37542f5a4af2a438e9490915e5b72"},
+ {file = "ninja-1.13.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:3d7d7779d12cb20c6d054c61b702139fd23a7a964ec8f2c823f1ab1b084150db"},
+ {file = "ninja-1.13.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:d741a5e6754e0bda767e3274a0f0deeef4807f1fec6c0d7921a0244018926ae5"},
+ {file = "ninja-1.13.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:e8bad11f8a00b64137e9b315b137d8bb6cbf3086fbdc43bf1f90fd33324d2e96"},
+ {file = "ninja-1.13.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b4f2a072db3c0f944c32793e91532d8948d20d9ab83da9c0c7c15b5768072200"},
+ {file = "ninja-1.13.0-py3-none-win32.whl", hash = "sha256:8cfbb80b4a53456ae8a39f90ae3d7a2129f45ea164f43fadfa15dc38c4aef1c9"},
+ {file = "ninja-1.13.0-py3-none-win_amd64.whl", hash = "sha256:fb8ee8719f8af47fed145cced4a85f0755dd55d45b2bddaf7431fa89803c5f3e"},
+ {file = "ninja-1.13.0-py3-none-win_arm64.whl", hash = "sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9"},
+ {file = "ninja-1.13.0.tar.gz", hash = "sha256:4a40ce995ded54d9dc24f8ea37ff3bf62ad192b547f6c7126e7e25045e76f978"},
+]
+
+[[package]]
+name = "numpy"
+version = "2.2.6"
+requires_python = ">=3.10"
+summary = "Fundamental package for array computing in Python"
+groups = ["default"]
+files = [
+ {file = "numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff"},
+ {file = "numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c"},
+ {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3"},
+ {file = "numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282"},
+ {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87"},
+ {file = "numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249"},
+ {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49"},
+ {file = "numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de"},
+ {file = "numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4"},
+ {file = "numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d"},
+ {file = "numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566"},
+ {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f"},
+ {file = "numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f"},
+ {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868"},
+ {file = "numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d"},
+ {file = "numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd"},
+ {file = "numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40"},
+ {file = "numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8"},
+ {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f"},
+ {file = "numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa"},
+ {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571"},
+ {file = "numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1"},
+ {file = "numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff"},
+ {file = "numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06"},
+ {file = "numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd"},
+]
+
+[[package]]
+name = "nvidia-cublas-cu12"
+version = "12.6.4.1"
+requires_python = ">=3"
+summary = "CUBLAS native runtime libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08ed2686e9875d01b58e3cb379c6896df8e76c75e0d4a7f7dace3d7b6d9ef8eb"},
+ {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:235f728d6e2a409eddf1df58d5b0921cf80cfa9e72b9f2775ccb7b4a87984668"},
+ {file = "nvidia_cublas_cu12-12.6.4.1-py3-none-win_amd64.whl", hash = "sha256:9e4fa264f4d8a4eb0cdbd34beadc029f453b3bafae02401e999cf3d5a5af75f8"},
+]
+
+[[package]]
+name = "nvidia-cuda-cupti-cu12"
+version = "12.6.80"
+requires_python = ">=3"
+summary = "CUDA profiling tools runtime libs."
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:166ee35a3ff1587f2490364f90eeeb8da06cd867bd5b701bf7f9a02b78bc63fc"},
+ {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_aarch64.whl", hash = "sha256:358b4a1d35370353d52e12f0a7d1769fc01ff74a191689d3870b2123156184c4"},
+ {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6768bad6cab4f19e8292125e5f1ac8aa7d1718704012a0e3272a6f61c4bce132"},
+ {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a3eff6cdfcc6a4c35db968a06fcadb061cbc7d6dde548609a941ff8701b98b73"},
+ {file = "nvidia_cuda_cupti_cu12-12.6.80-py3-none-win_amd64.whl", hash = "sha256:bbe6ae76e83ce5251b56e8c8e61a964f757175682bbad058b170b136266ab00a"},
+]
+
+[[package]]
+name = "nvidia-cuda-nvrtc-cu12"
+version = "12.6.77"
+requires_python = ">=3"
+summary = "NVRTC native runtime libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5847f1d6e5b757f1d2b3991a01082a44aad6f10ab3c5c0213fa3e25bddc25a13"},
+ {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:35b0cc6ee3a9636d5409133e79273ce1f3fd087abb0532d2d2e8fff1fe9efc53"},
+ {file = "nvidia_cuda_nvrtc_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:f7007dbd914c56bd80ea31bc43e8e149da38f68158f423ba845fc3292684e45a"},
+]
+
+[[package]]
+name = "nvidia-cuda-runtime-cu12"
+version = "12.6.77"
+requires_python = ">=3"
+summary = "CUDA Runtime native Libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6116fad3e049e04791c0256a9778c16237837c08b27ed8c8401e2e45de8d60cd"},
+ {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d461264ecb429c84c8879a7153499ddc7b19b5f8d84c204307491989a365588e"},
+ {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ba3b56a4f896141e25e19ab287cd71e52a6a0f4b29d0d31609f60e3b4d5219b7"},
+ {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a84d15d5e1da416dd4774cb42edf5e954a3e60cc945698dc1d5be02321c44dc8"},
+ {file = "nvidia_cuda_runtime_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:86c58044c824bf3c173c49a2dbc7a6c8b53cb4e4dca50068be0bf64e9dab3f7f"},
+]
+
+[[package]]
+name = "nvidia-cudnn-cu12"
+version = "9.5.1.17"
+requires_python = ">=3"
+summary = "cuDNN runtime libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+dependencies = [
+ "nvidia-cublas-cu12",
+]
+files = [
+ {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9fd4584468533c61873e5fda8ca41bac3a38bcb2d12350830c69b0a96a7e4def"},
+ {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:30ac3869f6db17d170e0e556dd6cc5eee02647abc31ca856634d5a40f82c15b2"},
+ {file = "nvidia_cudnn_cu12-9.5.1.17-py3-none-win_amd64.whl", hash = "sha256:d7af0f8a4f3b4b9dbb3122f2ef553b45694ed9c384d5a75bab197b8eefb79ab8"},
+]
+
+[[package]]
+name = "nvidia-cufft-cu12"
+version = "11.3.0.4"
+requires_python = ">=3"
+summary = "CUFFT native runtime libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+dependencies = [
+ "nvidia-nvjitlink-cu12",
+]
+files = [
+ {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d16079550df460376455cba121db6564089176d9bac9e4f360493ca4741b22a6"},
+ {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8510990de9f96c803a051822618d42bf6cb8f069ff3f48d93a8486efdacb48fb"},
+ {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ccba62eb9cef5559abd5e0d54ceed2d9934030f51163df018532142a8ec533e5"},
+ {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca"},
+ {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-win_amd64.whl", hash = "sha256:6048ebddfb90d09d2707efb1fd78d4e3a77cb3ae4dc60e19aab6be0ece2ae464"},
+]
+
+[[package]]
+name = "nvidia-cufile-cu12"
+version = "1.11.1.6"
+requires_python = ">=3"
+summary = "cuFile GPUDirect libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cc23469d1c7e52ce6c1d55253273d32c565dd22068647f3aa59b3c6b005bf159"},
+ {file = "nvidia_cufile_cu12-1.11.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:8f57a0051dcf2543f6dc2b98a98cb2719c37d3cee1baba8965d57f3bbc90d4db"},
+]
+
+[[package]]
+name = "nvidia-curand-cu12"
+version = "10.3.7.77"
+requires_python = ">=3"
+summary = "CURAND native runtime libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6e82df077060ea28e37f48a3ec442a8f47690c7499bff392a5938614b56c98d8"},
+ {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a42cd1344297f70b9e39a1e4f467a4e1c10f1da54ff7a85c12197f6c652c8bdf"},
+ {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117"},
+ {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:7b2ed8e95595c3591d984ea3603dd66fe6ce6812b886d59049988a712ed06b6e"},
+ {file = "nvidia_curand_cu12-10.3.7.77-py3-none-win_amd64.whl", hash = "sha256:6d6d935ffba0f3d439b7cd968192ff068fafd9018dbf1b85b37261b13cfc9905"},
+]
+
+[[package]]
+name = "nvidia-cusolver-cu12"
+version = "11.7.1.2"
+requires_python = ">=3"
+summary = "CUDA solver native runtime libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+dependencies = [
+ "nvidia-cublas-cu12",
+ "nvidia-cusparse-cu12",
+ "nvidia-nvjitlink-cu12",
+]
+files = [
+ {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0ce237ef60acde1efc457335a2ddadfd7610b892d94efee7b776c64bb1cac9e0"},
+ {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e9e49843a7707e42022babb9bcfa33c29857a93b88020c4e4434656a655b698c"},
+ {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6"},
+ {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:dbbe4fc38ec1289c7e5230e16248365e375c3673c9c8bac5796e2e20db07f56e"},
+ {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-win_amd64.whl", hash = "sha256:6813f9d8073f555444a8705f3ab0296d3e1cb37a16d694c5fc8b862a0d8706d7"},
+]
+
+[[package]]
+name = "nvidia-cusparse-cu12"
+version = "12.5.4.2"
+requires_python = ">=3"
+summary = "CUSPARSE native runtime libraries"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+dependencies = [
+ "nvidia-nvjitlink-cu12",
+]
+files = [
+ {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d25b62fb18751758fe3c93a4a08eff08effedfe4edf1c6bb5afd0890fe88f887"},
+ {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7aa32fa5470cf754f72d1116c7cbc300b4e638d3ae5304cfa4a638a5b87161b1"},
+ {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7556d9eca156e18184b94947ade0fba5bb47d69cec46bf8660fd2c71a4b48b73"},
+ {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f"},
+ {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-win_amd64.whl", hash = "sha256:4acb8c08855a26d737398cba8fb6f8f5045d93f82612b4cfd84645a2332ccf20"},
+]
+
+[[package]]
+name = "nvidia-cusparselt-cu12"
+version = "0.6.3"
+summary = "NVIDIA cuSPARSELt"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8371549623ba601a06322af2133c4a44350575f5a3108fb75f3ef20b822ad5f1"},
+ {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e5c8a26c36445dd2e6812f1177978a24e2d37cacce7e090f297a688d1ec44f46"},
+ {file = "nvidia_cusparselt_cu12-0.6.3-py3-none-win_amd64.whl", hash = "sha256:3b325bcbd9b754ba43df5a311488fca11a6b5dc3d11df4d190c000cf1a0765c7"},
+]
+
+[[package]]
+name = "nvidia-nccl-cu12"
+version = "2.26.2"
+requires_python = ">=3"
+summary = "NVIDIA Collective Communication Library (NCCL) Runtime"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c196e95e832ad30fbbb50381eb3cbd1fadd5675e587a548563993609af19522"},
+ {file = "nvidia_nccl_cu12-2.26.2-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:694cf3879a206553cc9d7dbda76b13efaf610fdb70a50cba303de1b0d1530ac6"},
+]
+
+[[package]]
+name = "nvidia-nvjitlink-cu12"
+version = "12.6.85"
+requires_python = ">=3"
+summary = "Nvidia JIT LTO Library"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:eedc36df9e88b682efe4309aa16b5b4e78c2407eac59e8c10a6a47535164369a"},
+ {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cf4eaa7d4b6b543ffd69d6abfb11efdeb2db48270d94dfd3a452c24150829e41"},
+ {file = "nvidia_nvjitlink_cu12-12.6.85-py3-none-win_amd64.whl", hash = "sha256:e61120e52ed675747825cdd16febc6a0730537451d867ee58bee3853b1b13d1c"},
+]
+
+[[package]]
+name = "nvidia-nvtx-cu12"
+version = "12.6.77"
+requires_python = ">=3"
+summary = "NVIDIA Tools Extension"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+files = [
+ {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f44f8d86bb7d5629988d61c8d3ae61dddb2015dee142740536bc7481b022fe4b"},
+ {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:adcaabb9d436c9761fca2b13959a2d237c5f9fd406c8e4b723c695409ff88059"},
+ {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b90bed3df379fa79afbd21be8e04a0314336b8ae16768b58f2d34cb1d04cd7d2"},
+ {file = "nvidia_nvtx_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6574241a3ec5fdc9334353ab8c479fe75841dbe8f4532a8fc97ce63503330ba1"},
+ {file = "nvidia_nvtx_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:2fb11a4af04a5e6c84073e6404d26588a34afd35379f0855a99797897efa75c0"},
+]
+
+[[package]]
+name = "openai"
+version = "1.99.9"
+requires_python = ">=3.8"
+summary = "The official Python library for the openai API"
+groups = ["default"]
+dependencies = [
+ "anyio<5,>=3.5.0",
+ "distro<2,>=1.7.0",
+ "httpx<1,>=0.23.0",
+ "jiter<1,>=0.4.0",
+ "pydantic<3,>=1.9.0",
+ "sniffio",
+ "tqdm>4",
+ "typing-extensions<5,>=4.11",
+]
+files = [
+ {file = "openai-1.99.9-py3-none-any.whl", hash = "sha256:9dbcdb425553bae1ac5d947147bebbd630d91bbfc7788394d4c4f3a35682ab3a"},
+ {file = "openai-1.99.9.tar.gz", hash = "sha256:f2082d155b1ad22e83247c3de3958eb4255b20ccf4a1de2e6681b6957b554e92"},
+]
+
+[[package]]
+name = "opencv-python"
+version = "4.12.0.88"
+requires_python = ">=3.6"
+summary = "Wrapper package for OpenCV python bindings."
+groups = ["default"]
+dependencies = [
+ "numpy<2.0; python_version < \"3.9\"",
+ "numpy<2.3.0,>=2; python_version >= \"3.9\"",
+]
+files = [
+ {file = "opencv-python-4.12.0.88.tar.gz", hash = "sha256:8b738389cede219405f6f3880b851efa3415ccd674752219377353f017d2994d"},
+ {file = "opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:f9a1f08883257b95a5764bf517a32d75aec325319c8ed0f89739a57fae9e92a5"},
+ {file = "opencv_python-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:812eb116ad2b4de43ee116fcd8991c3a687f099ada0b04e68f64899c09448e81"},
+ {file = "opencv_python-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:51fd981c7df6af3e8f70b1556696b05224c4e6b6777bdd2a46b3d4fb09de1a92"},
+ {file = "opencv_python-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:092c16da4c5a163a818f120c22c5e4a2f96e0db4f24e659c701f1fe629a690f9"},
+ {file = "opencv_python-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:ff554d3f725b39878ac6a2e1fa232ec509c36130927afc18a1719ebf4fbf4357"},
+ {file = "opencv_python-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:d98edb20aa932fd8ebd276a72627dad9dc097695b3d435a4257557bbb49a79d2"},
+]
+
+[[package]]
+name = "opencv-python-headless"
+version = "4.12.0.88"
+requires_python = ">=3.6"
+summary = "Wrapper package for OpenCV python bindings."
+groups = ["default"]
+dependencies = [
+ "numpy<2.0; python_version < \"3.9\"",
+ "numpy<2.3.0,>=2; python_version >= \"3.9\"",
+]
+files = [
+ {file = "opencv-python-headless-4.12.0.88.tar.gz", hash = "sha256:cfdc017ddf2e59b6c2f53bc12d74b6b0be7ded4ec59083ea70763921af2b6c09"},
+ {file = "opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1e58d664809b3350c1123484dd441e1667cd7bed3086db1b9ea1b6f6cb20b50e"},
+ {file = "opencv_python_headless-4.12.0.88-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:365bb2e486b50feffc2d07a405b953a8f3e8eaa63865bc650034e5c71e7a5154"},
+ {file = "opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:aeb4b13ecb8b4a0beb2668ea07928160ea7c2cd2d9b5ef571bbee6bafe9cc8d0"},
+ {file = "opencv_python_headless-4.12.0.88-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:236c8df54a90f4d02076e6f9c1cc763d794542e886c576a6fee46ec8ff75a7a9"},
+ {file = "opencv_python_headless-4.12.0.88-cp37-abi3-win32.whl", hash = "sha256:fde2cf5c51e4def5f2132d78e0c08f9c14783cd67356922182c6845b9af87dbd"},
+ {file = "opencv_python_headless-4.12.0.88-cp37-abi3-win_amd64.whl", hash = "sha256:86b413bdd6c6bf497832e346cd5371995de148e579b9774f8eba686dee3f5528"},
+]
+
+[[package]]
+name = "orjson"
+version = "3.11.2"
+requires_python = ">=3.9"
+summary = ""
+groups = ["default"]
+files = [
+ {file = "orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486"},
+ {file = "orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1"},
+ {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131"},
+ {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c"},
+ {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14"},
+ {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448"},
+ {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c"},
+ {file = "orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804"},
+ {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307"},
+ {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219"},
+ {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45"},
+ {file = "orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e"},
+ {file = "orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e"},
+ {file = "orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732"},
+ {file = "orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36"},
+ {file = "orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219"},
+ {file = "orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad"},
+ {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2"},
+ {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe"},
+ {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae"},
+ {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6"},
+ {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1"},
+ {file = "orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa"},
+ {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e"},
+ {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15"},
+ {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac"},
+ {file = "orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8"},
+ {file = "orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5"},
+ {file = "orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d"},
+ {file = "orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535"},
+ {file = "orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7"},
+ {file = "orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81"},
+ {file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f"},
+ {file = "orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7"},
+ {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4"},
+ {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f"},
+ {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7"},
+ {file = "orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6"},
+ {file = "orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f"},
+ {file = "orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8"},
+ {file = "orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67"},
+ {file = "orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309"},
+]
+
+[[package]]
+name = "packaging"
+version = "25.0"
+requires_python = ">=3.8"
+summary = "Core utilities for Python packages"
+groups = ["default", "dev"]
+files = [
+ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
+ {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
+]
+
+[[package]]
+name = "pandas"
+version = "2.3.1"
+requires_python = ">=3.9"
+summary = "Powerful data structures for data analysis, time series, and statistics"
+groups = ["default"]
+dependencies = [
+ "numpy>=1.22.4; python_version < \"3.11\"",
+ "numpy>=1.23.2; python_version == \"3.11\"",
+ "numpy>=1.26.0; python_version >= \"3.12\"",
+ "python-dateutil>=2.8.2",
+ "pytz>=2020.1",
+ "tzdata>=2022.7",
+]
+files = [
+ {file = "pandas-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:689968e841136f9e542020698ee1c4fbe9caa2ed2213ae2388dc7b81721510d3"},
+ {file = "pandas-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:025e92411c16cbe5bb2a4abc99732a6b132f439b8aab23a59fa593eb00704232"},
+ {file = "pandas-2.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b7ff55f31c4fcb3e316e8f7fa194566b286d6ac430afec0d461163312c5841e"},
+ {file = "pandas-2.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dcb79bf373a47d2a40cf7232928eb7540155abbc460925c2c96d2d30b006eb4"},
+ {file = "pandas-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:56a342b231e8862c96bdb6ab97170e203ce511f4d0429589c8ede1ee8ece48b8"},
+ {file = "pandas-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ca7ed14832bce68baef331f4d7f294411bed8efd032f8109d690df45e00c4679"},
+ {file = "pandas-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ac942bfd0aca577bef61f2bc8da8147c4ef6879965ef883d8e8d5d2dc3e744b8"},
+ {file = "pandas-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9026bd4a80108fac2239294a15ef9003c4ee191a0f64b90f170b40cfb7cf2d22"},
+ {file = "pandas-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6de8547d4fdb12421e2d047a2c446c623ff4c11f47fddb6b9169eb98ffba485a"},
+ {file = "pandas-2.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:782647ddc63c83133b2506912cc6b108140a38a37292102aaa19c81c83db2928"},
+ {file = "pandas-2.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba6aff74075311fc88504b1db890187a3cd0f887a5b10f5525f8e2ef55bfdb9"},
+ {file = "pandas-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e5635178b387bd2ba4ac040f82bc2ef6e6b500483975c4ebacd34bec945fda12"},
+ {file = "pandas-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6f3bf5ec947526106399a9e1d26d40ee2b259c66422efdf4de63c848492d91bb"},
+ {file = "pandas-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:1c78cf43c8fde236342a1cb2c34bcff89564a7bfed7e474ed2fffa6aed03a956"},
+ {file = "pandas-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8dfc17328e8da77be3cf9f47509e5637ba8f137148ed0e9b5241e1baf526e20a"},
+ {file = "pandas-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ec6c851509364c59a5344458ab935e6451b31b818be467eb24b0fe89bd05b6b9"},
+ {file = "pandas-2.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:911580460fc4884d9b05254b38a6bfadddfcc6aaef856fb5859e7ca202e45275"},
+ {file = "pandas-2.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f4d6feeba91744872a600e6edbbd5b033005b431d5ae8379abee5bcfa479fab"},
+ {file = "pandas-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fe37e757f462d31a9cd7580236a82f353f5713a80e059a29753cf938c6775d96"},
+ {file = "pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444"},
+ {file = "pandas-2.3.1.tar.gz", hash = "sha256:0a95b9ac964fe83ce317827f80304d37388ea77616b1425f0ae41c9d2d0d7bb2"},
+]
+
+[[package]]
+name = "pathspec"
+version = "0.12.1"
+requires_python = ">=3.8"
+summary = "Utility library for gitignore style pattern matching of file paths."
+groups = ["dev"]
+files = [
+ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
+ {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
+]
+
+[[package]]
+name = "pillow"
+version = "11.3.0"
+requires_python = ">=3.9"
+summary = "Python Imaging Library (Fork)"
+groups = ["default"]
+files = [
+ {file = "pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4"},
+ {file = "pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7"},
+ {file = "pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024"},
+ {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809"},
+ {file = "pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d"},
+ {file = "pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149"},
+ {file = "pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d"},
+ {file = "pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542"},
+ {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:1c627742b539bba4309df89171356fcb3cc5a9178355b2727d1b74a6cf155fbd"},
+ {file = "pillow-11.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:30b7c02f3899d10f13d7a48163c8969e4e653f8b43416d23d13d1bbfdc93b9f8"},
+ {file = "pillow-11.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:7859a4cc7c9295f5838015d8cc0a9c215b77e43d07a25e460f35cf516df8626f"},
+ {file = "pillow-11.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec1ee50470b0d050984394423d96325b744d55c701a439d2bd66089bff963d3c"},
+ {file = "pillow-11.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7db51d222548ccfd274e4572fdbf3e810a5e66b00608862f947b163e613b67dd"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2d6fcc902a24ac74495df63faad1884282239265c6839a0a6416d33faedfae7e"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f0f5d8f4a08090c6d6d578351a2b91acf519a54986c055af27e7a93feae6d3f1"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c37d8ba9411d6003bba9e518db0db0c58a680ab9fe5179f040b0463644bc9805"},
+ {file = "pillow-11.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13f87d581e71d9189ab21fe0efb5a23e9f28552d5be6979e84001d3b8505abe8"},
+ {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:023f6d2d11784a465f09fd09a34b150ea4672e85fb3d05931d89f373ab14abb2"},
+ {file = "pillow-11.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:45dfc51ac5975b938e9809451c51734124e73b04d0f0ac621649821a63852e7b"},
+ {file = "pillow-11.3.0-cp313-cp313-win32.whl", hash = "sha256:a4d336baed65d50d37b88ca5b60c0fa9d81e3a87d4a7930d3880d1624d5b31f3"},
+ {file = "pillow-11.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bce5c4fd0921f99d2e858dc4d4d64193407e1b99478bc5cacecba2311abde51"},
+ {file = "pillow-11.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:1904e1264881f682f02b7f8167935cce37bc97db457f8e7849dc3a6a52b99580"},
+ {file = "pillow-11.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4c834a3921375c48ee6b9624061076bc0a32a60b5532b322cc0ea64e639dd50e"},
+ {file = "pillow-11.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5e05688ccef30ea69b9317a9ead994b93975104a677a36a8ed8106be9260aa6d"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1019b04af07fc0163e2810167918cb5add8d74674b6267616021ab558dc98ced"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f944255db153ebb2b19c51fe85dd99ef0ce494123f21b9db4877ffdfc5590c7c"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f85acb69adf2aaee8b7da124efebbdb959a104db34d3a2cb0f3793dbae422a8"},
+ {file = "pillow-11.3.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:05f6ecbeff5005399bb48d198f098a9b4b6bdf27b8487c7f38ca16eeb070cd59"},
+ {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a7bc6e6fd0395bc052f16b1a8670859964dbd7003bd0af2ff08342eb6e442cfe"},
+ {file = "pillow-11.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:83e1b0161c9d148125083a35c1c5a89db5b7054834fd4387499e06552035236c"},
+ {file = "pillow-11.3.0-cp313-cp313t-win32.whl", hash = "sha256:2a3117c06b8fb646639dce83694f2f9eac405472713fcb1ae887469c0d4f6788"},
+ {file = "pillow-11.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:857844335c95bea93fb39e0fa2726b4d9d758850b34075a7e3ff4f4fa3aa3b31"},
+ {file = "pillow-11.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:8797edc41f3e8536ae4b10897ee2f637235c94f27404cac7297f7b607dd0716e"},
+ {file = "pillow-11.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d9da3df5f9ea2a89b81bb6087177fb1f4d1c7146d583a3fe5c672c0d94e55e12"},
+ {file = "pillow-11.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0b275ff9b04df7b640c59ec5a3cb113eefd3795a8df80bac69646ef699c6981a"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0743841cabd3dba6a83f38a92672cccbd69af56e3e91777b0ee7f4dba4385632"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2465a69cf967b8b49ee1b96d76718cd98c4e925414ead59fdf75cf0fd07df673"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41742638139424703b4d01665b807c6468e23e699e8e90cffefe291c5832b027"},
+ {file = "pillow-11.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:93efb0b4de7e340d99057415c749175e24c8864302369e05914682ba642e5d77"},
+ {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7966e38dcd0fa11ca390aed7c6f20454443581d758242023cf36fcb319b1a874"},
+ {file = "pillow-11.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:98a9afa7b9007c67ed84c57c9e0ad86a6000da96eaa638e4f8abe5b65ff83f0a"},
+ {file = "pillow-11.3.0-cp314-cp314-win32.whl", hash = "sha256:02a723e6bf909e7cea0dac1b0e0310be9d7650cd66222a5f1c571455c0a45214"},
+ {file = "pillow-11.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:a418486160228f64dd9e9efcd132679b7a02a5f22c982c78b6fc7dab3fefb635"},
+ {file = "pillow-11.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:155658efb5e044669c08896c0c44231c5e9abcaadbc5cd3648df2f7c0b96b9a6"},
+ {file = "pillow-11.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:59a03cdf019efbfeeed910bf79c7c93255c3d54bc45898ac2a4140071b02b4ae"},
+ {file = "pillow-11.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f8a5827f84d973d8636e9dc5764af4f0cf2318d26744b3d902931701b0d46653"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ee92f2fd10f4adc4b43d07ec5e779932b4eb3dbfbc34790ada5a6669bc095aa6"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c96d333dcf42d01f47b37e0979b6bd73ec91eae18614864622d9b87bbd5bbf36"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c96f993ab8c98460cd0c001447bff6194403e8b1d7e149ade5f00594918128b"},
+ {file = "pillow-11.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41342b64afeba938edb034d122b2dda5db2139b9a4af999729ba8818e0056477"},
+ {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:068d9c39a2d1b358eb9f245ce7ab1b5c3246c7c8c7d9ba58cfa5b43146c06e50"},
+ {file = "pillow-11.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a1bc6ba083b145187f648b667e05a2534ecc4b9f2784c2cbe3089e44868f2b9b"},
+ {file = "pillow-11.3.0-cp314-cp314t-win32.whl", hash = "sha256:118ca10c0d60b06d006be10a501fd6bbdfef559251ed31b794668ed569c87e12"},
+ {file = "pillow-11.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8924748b688aa210d79883357d102cd64690e56b923a186f35a82cbc10f997db"},
+ {file = "pillow-11.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:79ea0d14d3ebad43ec77ad5272e6ff9bba5b679ef73375ea760261207fa8e0aa"},
+ {file = "pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.3.8"
+requires_python = ">=3.9"
+summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+groups = ["dev"]
+files = [
+ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"},
+ {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"},
+]
+
+[[package]]
+name = "pluggy"
+version = "1.6.0"
+requires_python = ">=3.9"
+summary = "plugin and hook calling mechanisms for python"
+groups = ["dev"]
+files = [
+ {file = "pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746"},
+ {file = "pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3"},
+]
+
+[[package]]
+name = "posthog"
+version = "6.5.0"
+requires_python = ">=3.9"
+summary = "Integrate PostHog into any python application."
+groups = ["default"]
+dependencies = [
+ "backoff>=1.10.0",
+ "distro>=1.5.0",
+ "python-dateutil>=2.2",
+ "requests<3.0,>=2.7",
+ "six>=1.5",
+ "typing-extensions>=4.2.0",
+]
+files = [
+ {file = "posthog-6.5.0-py3-none-any.whl", hash = "sha256:1376f85c5382eae0985dd1ad48f2e6d7db18c6bf52cef3b4d8ff448a955bdb9f"},
+ {file = "posthog-6.5.0.tar.gz", hash = "sha256:aa5fe322c30384b302c79c49aee59c8dce6fee84a5fa78734103cab177e4e640"},
+]
+
+[[package]]
+name = "propcache"
+version = "0.3.2"
+requires_python = ">=3.9"
+summary = "Accelerated property cache"
+groups = ["default"]
+files = [
+ {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10"},
+ {file = "propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154"},
+ {file = "propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67"},
+ {file = "propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06"},
+ {file = "propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1"},
+ {file = "propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1"},
+ {file = "propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c"},
+ {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ca592ed634a73ca002967458187109265e980422116c0a107cf93d81f95af945"},
+ {file = "propcache-0.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9ecb0aad4020e275652ba3975740f241bd12a61f1a784df044cf7477a02bc252"},
+ {file = "propcache-0.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7f08f1cc28bd2eade7a8a3d2954ccc673bb02062e3e7da09bc75d843386b342f"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1a342c834734edb4be5ecb1e9fb48cb64b1e2320fccbd8c54bf8da8f2a84c33"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a544caaae1ac73f1fecfae70ded3e93728831affebd017d53449e3ac052ac1e"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:310d11aa44635298397db47a3ebce7db99a4cc4b9bbdfcf6c98a60c8d5261cf1"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1396592321ac83157ac03a2023aa6cc4a3cc3cfdecb71090054c09e5a7cce3"},
+ {file = "propcache-0.3.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cabf5b5902272565e78197edb682017d21cf3b550ba0460ee473753f28d23c1"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0a2f2235ac46a7aa25bdeb03a9e7060f6ecbd213b1f9101c43b3090ffb971ef6"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:92b69e12e34869a6970fd2f3da91669899994b47c98f5d430b781c26f1d9f387"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:54e02207c79968ebbdffc169591009f4474dde3b4679e16634d34c9363ff56b4"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4adfb44cb588001f68c5466579d3f1157ca07f7504fc91ec87862e2b8e556b88"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fd3e6019dc1261cd0291ee8919dd91fbab7b169bb76aeef6c716833a3f65d206"},
+ {file = "propcache-0.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4c181cad81158d71c41a2bce88edce078458e2dd5ffee7eddd6b05da85079f43"},
+ {file = "propcache-0.3.2-cp313-cp313-win32.whl", hash = "sha256:8a08154613f2249519e549de2330cf8e2071c2887309a7b07fb56098f5170a02"},
+ {file = "propcache-0.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:e41671f1594fc4ab0a6dec1351864713cb3a279910ae8b58f884a88a0a632c05"},
+ {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:9a3cf035bbaf035f109987d9d55dc90e4b0e36e04bbbb95af3055ef17194057b"},
+ {file = "propcache-0.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:156c03d07dc1323d8dacaa221fbe028c5c70d16709cdd63502778e6c3ccca1b0"},
+ {file = "propcache-0.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74413c0ba02ba86f55cf60d18daab219f7e531620c15f1e23d95563f505efe7e"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f066b437bb3fa39c58ff97ab2ca351db465157d68ed0440abecb21715eb24b28"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1304b085c83067914721e7e9d9917d41ad87696bf70f0bc7dee450e9c71ad0a"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab50cef01b372763a13333b4e54021bdcb291fc9a8e2ccb9c2df98be51bcde6c"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fad3b2a085ec259ad2c2842666b2a0a49dea8463579c606426128925af1ed725"},
+ {file = "propcache-0.3.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:261fa020c1c14deafd54c76b014956e2f86991af198c51139faf41c4d5e83892"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:46d7f8aa79c927e5f987ee3a80205c987717d3659f035c85cf0c3680526bdb44"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:6d8f3f0eebf73e3c0ff0e7853f68be638b4043c65a70517bb575eff54edd8dbe"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:03c89c1b14a5452cf15403e291c0ccd7751d5b9736ecb2c5bab977ad6c5bcd81"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:0cc17efde71e12bbaad086d679ce575268d70bc123a5a71ea7ad76f70ba30bba"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:acdf05d00696bc0447e278bb53cb04ca72354e562cf88ea6f9107df8e7fd9770"},
+ {file = "propcache-0.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4445542398bd0b5d32df908031cb1b30d43ac848e20470a878b770ec2dcc6330"},
+ {file = "propcache-0.3.2-cp313-cp313t-win32.whl", hash = "sha256:f86e5d7cd03afb3a1db8e9f9f6eff15794e79e791350ac48a8c924e6f439f394"},
+ {file = "propcache-0.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:9704bedf6e7cbe3c65eca4379a9b53ee6a83749f047808cbb5044d40d7d72198"},
+ {file = "propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f"},
+ {file = "propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168"},
+]
+
+[[package]]
+name = "protobuf"
+version = "6.32.0"
+requires_python = ">=3.9"
+summary = ""
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+files = [
+ {file = "protobuf-6.32.0-cp310-abi3-win32.whl", hash = "sha256:84f9e3c1ff6fb0308dbacb0950d8aa90694b0d0ee68e75719cb044b7078fe741"},
+ {file = "protobuf-6.32.0-cp310-abi3-win_amd64.whl", hash = "sha256:a8bdbb2f009cfc22a36d031f22a625a38b615b5e19e558a7b756b3279723e68e"},
+ {file = "protobuf-6.32.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d52691e5bee6c860fff9a1c86ad26a13afbeb4b168cd4445c922b7e2cf85aaf0"},
+ {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:501fe6372fd1c8ea2a30b4d9be8f87955a64d6be9c88a973996cef5ef6f0abf1"},
+ {file = "protobuf-6.32.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:75a2aab2bd1aeb1f5dc7c5f33bcb11d82ea8c055c9becbb41c26a8c43fd7092c"},
+ {file = "protobuf-6.32.0-py3-none-any.whl", hash = "sha256:ba377e5b67b908c8f3072a57b63e2c6a4cbd18aea4ed98d2584350dbf46f2783"},
+ {file = "protobuf-6.32.0.tar.gz", hash = "sha256:a81439049127067fc49ec1d36e25c6ee1d1a2b7be930675f919258d03c04e7d2"},
+]
+
+[[package]]
+name = "psutil"
+version = "7.0.0"
+requires_python = ">=3.6"
+summary = "Cross-platform lib for process and system monitoring in Python. NOTE: the syntax of this script MUST be kept compatible with Python 2.7."
+groups = ["default"]
+files = [
+ {file = "psutil-7.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:101d71dc322e3cffd7cea0650b09b3d08b8e7c4109dd6809fe452dfd00e58b25"},
+ {file = "psutil-7.0.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:39db632f6bb862eeccf56660871433e111b6ea58f2caea825571951d4b6aa3da"},
+ {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fcee592b4c6f146991ca55919ea3d1f8926497a713ed7faaf8225e174581e91"},
+ {file = "psutil-7.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b1388a4f6875d7e2aff5c4ca1cc16c545ed41dd8bb596cefea80111db353a34"},
+ {file = "psutil-7.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5f098451abc2828f7dc6b58d44b532b22f2088f4999a937557b603ce72b1993"},
+ {file = "psutil-7.0.0-cp37-abi3-win32.whl", hash = "sha256:ba3fcef7523064a6c9da440fc4d6bd07da93ac726b5733c29027d7dc95b39d99"},
+ {file = "psutil-7.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:4cf3d4eb1aa9b348dec30105c55cd9b7d4629285735a102beb4441e38db90553"},
+ {file = "psutil-7.0.0.tar.gz", hash = "sha256:7be9c3eba38beccb6495ea33afd982a44074b78f28c434a1f51cc07fd315c456"},
+]
+
+[[package]]
+name = "py-cpuinfo"
+version = "9.0.0"
+summary = "Get CPU info with pure Python"
+groups = ["default"]
+files = [
+ {file = "py-cpuinfo-9.0.0.tar.gz", hash = "sha256:3cdbbf3fac90dc6f118bfd64384f309edeadd902d7c8fb17f02ffa1fc3f49690"},
+ {file = "py_cpuinfo-9.0.0-py3-none-any.whl", hash = "sha256:859625bc251f64e21f077d099d4162689c762b5d6a4c3c97553d56241c9674d5"},
+]
+
+[[package]]
+name = "pyarrow"
+version = "21.0.0"
+requires_python = ">=3.9"
+summary = "Python library for Apache Arrow"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+files = [
+ {file = "pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd"},
+ {file = "pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876"},
+ {file = "pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d"},
+ {file = "pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e"},
+ {file = "pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82"},
+ {file = "pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623"},
+ {file = "pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18"},
+ {file = "pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a"},
+ {file = "pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe"},
+ {file = "pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd"},
+ {file = "pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61"},
+ {file = "pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d"},
+ {file = "pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99"},
+ {file = "pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636"},
+ {file = "pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da"},
+ {file = "pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7"},
+ {file = "pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6"},
+ {file = "pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8"},
+ {file = "pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503"},
+ {file = "pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79"},
+ {file = "pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10"},
+ {file = "pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc"},
+]
+
+[[package]]
+name = "pyclipper"
+version = "1.3.0.post6"
+summary = "Cython wrapper for the C++ translation of the Angus Johnson's Clipper library (ver. 6.4.2)"
+groups = ["default"]
+files = [
+ {file = "pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6363b9d79ba1b5d8f32d1623e797c1e9f994600943402e68d5266067bdde173e"},
+ {file = "pyclipper-1.3.0.post6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:32cd7fb9c1c893eb87f82a072dbb5e26224ea7cebbad9dc306d67e1ac62dd229"},
+ {file = "pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3aab10e3c10ed8fa60c608fb87c040089b83325c937f98f06450cf9fcfdaf1d"},
+ {file = "pyclipper-1.3.0.post6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58eae2ff92a8cae1331568df076c4c5775bf946afab0068b217f0cf8e188eb3c"},
+ {file = "pyclipper-1.3.0.post6-cp312-cp312-win32.whl", hash = "sha256:793b0aa54b914257aa7dc76b793dd4dcfb3c84011d48df7e41ba02b571616eaf"},
+ {file = "pyclipper-1.3.0.post6-cp312-cp312-win_amd64.whl", hash = "sha256:d3f9da96f83b8892504923beb21a481cd4516c19be1d39eb57a92ef1c9a29548"},
+ {file = "pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f129284d2c7bcd213d11c0f35e1ae506a1144ce4954e9d1734d63b120b0a1b58"},
+ {file = "pyclipper-1.3.0.post6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:188fbfd1d30d02247f92c25ce856f5f3c75d841251f43367dbcf10935bc48f38"},
+ {file = "pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6d129d0c2587f2f5904d201a4021f859afbb45fada4261c9fdedb2205b09d23"},
+ {file = "pyclipper-1.3.0.post6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c9c80b5c46eef38ba3f12dd818dc87f5f2a0853ba914b6f91b133232315f526"},
+ {file = "pyclipper-1.3.0.post6-cp313-cp313-win32.whl", hash = "sha256:b15113ec4fc423b58e9ae80aa95cf5a0802f02d8f02a98a46af3d7d66ff0cc0e"},
+ {file = "pyclipper-1.3.0.post6-cp313-cp313-win_amd64.whl", hash = "sha256:e5ff68fa770ac654c7974fc78792978796f068bd274e95930c0691c31e192889"},
+ {file = "pyclipper-1.3.0.post6.tar.gz", hash = "sha256:42bff0102fa7a7f2abdd795a2594654d62b786d0c6cd67b72d469114fdeb608c"},
+]
+
+[[package]]
+name = "pycparser"
+version = "2.22"
+requires_python = ">=3.8"
+summary = "C parser in Python"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+files = [
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+]
+
+[[package]]
+name = "pydantic"
+version = "2.11.7"
+requires_python = ">=3.9"
+summary = "Data validation using Python type hints"
+groups = ["default"]
+dependencies = [
+ "annotated-types>=0.6.0",
+ "pydantic-core==2.33.2",
+ "typing-extensions>=4.12.2",
+ "typing-inspection>=0.4.0",
+]
+files = [
+ {file = "pydantic-2.11.7-py3-none-any.whl", hash = "sha256:dde5df002701f6de26248661f6835bbe296a47bf73990135c7d07ce741b9623b"},
+ {file = "pydantic-2.11.7.tar.gz", hash = "sha256:d989c3c6cb79469287b1569f7447a17848c998458d49ebe294e975b9baf0f0db"},
+]
+
+[[package]]
+name = "pydantic-core"
+version = "2.33.2"
+requires_python = ">=3.9"
+summary = "Core functionality for Pydantic validation and serialization"
+groups = ["default"]
+dependencies = [
+ "typing-extensions!=4.7.0,>=4.6.0",
+]
+files = [
+ {file = "pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2"},
+ {file = "pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1082dd3e2d7109ad8b7da48e1d4710c8d06c253cbc4a27c1cff4fbcaa97a9e3f"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f517ca031dfc037a9c07e748cefd8d96235088b83b4f4ba8939105d20fa1dcd6"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a9f2c9dd19656823cb8250b0724ee9c60a82f3cdf68a080979d13092a3b0fef"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2b0a451c263b01acebe51895bfb0e1cc842a5c666efe06cdf13846c7418caa9a"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ea40a64d23faa25e62a70ad163571c0b342b8bf66d5fa612ac0dec4f069d916"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb2d542b4d66f9470e8065c5469ec676978d625a8b7a363f07d9a501a9cb36a"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdac5d6ffa1b5a83bca06ffe7583f5576555e6c8b3a91fbd25ea7780f825f7d"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04a1a413977ab517154eebb2d326da71638271477d6ad87a769102f7c2488c56"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c8e7af2f4e0194c22b5b37205bfb293d166a7344a5b0d0eaccebc376546d77d5"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:5c92edd15cd58b3c2d34873597a1e20f13094f59cf88068adb18947df5455b4e"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:65132b7b4a1c0beded5e057324b7e16e10910c106d43675d9bd87d4f38dde162"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-win32.whl", hash = "sha256:52fb90784e0a242bb96ec53f42196a17278855b0f31ac7c3cc6f5c1ec4811849"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-win_amd64.whl", hash = "sha256:c083a3bdd5a93dfe480f1125926afcdbf2917ae714bdb80b36d34318b2bec5d9"},
+ {file = "pydantic_core-2.33.2-cp313-cp313-win_arm64.whl", hash = "sha256:e80b087132752f6b3d714f041ccf74403799d3b23a72722ea2e6ba2e892555b9"},
+ {file = "pydantic_core-2.33.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61c18fba8e5e9db3ab908620af374db0ac1baa69f0f32df4f61ae23f15e586ac"},
+ {file = "pydantic_core-2.33.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95237e53bb015f67b63c91af7518a62a8660376a6a0db19b89acc77a4d6199f5"},
+ {file = "pydantic_core-2.33.2-cp313-cp313t-win_amd64.whl", hash = "sha256:c2fc0a768ef76c15ab9238afa6da7f69895bb5d1ee83aeea2e3509af4472d0b9"},
+ {file = "pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc"},
+]
+
+[[package]]
+name = "pydub"
+version = "0.25.1"
+summary = "Manipulate audio with an simple and easy high level interface"
+groups = ["default"]
+files = [
+ {file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"},
+ {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"},
+]
+
+[[package]]
+name = "pygments"
+version = "2.19.2"
+requires_python = ">=3.8"
+summary = "Pygments is a syntax highlighting package written in Python."
+groups = ["default", "dev"]
+files = [
+ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
+ {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
+]
+
+[[package]]
+name = "pyparsing"
+version = "3.2.3"
+requires_python = ">=3.9"
+summary = "pyparsing module - Classes and methods to define and execute parsing grammars"
+groups = ["default"]
+files = [
+ {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"},
+ {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"},
+]
+
+[[package]]
+name = "pytest"
+version = "8.4.1"
+requires_python = ">=3.9"
+summary = "pytest: simple powerful testing with Python"
+groups = ["dev"]
+dependencies = [
+ "colorama>=0.4; sys_platform == \"win32\"",
+ "exceptiongroup>=1; python_version < \"3.11\"",
+ "iniconfig>=1",
+ "packaging>=20",
+ "pluggy<2,>=1.5",
+ "pygments>=2.7.2",
+ "tomli>=1; python_version < \"3.11\"",
+]
+files = [
+ {file = "pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7"},
+ {file = "pytest-8.4.1.tar.gz", hash = "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c"},
+]
+
+[[package]]
+name = "pytest-asyncio"
+version = "1.1.0"
+requires_python = ">=3.9"
+summary = "Pytest support for asyncio"
+groups = ["dev"]
+dependencies = [
+ "backports-asyncio-runner<2,>=1.1; python_version < \"3.11\"",
+ "pytest<9,>=8.2",
+ "typing-extensions>=4.12; python_version < \"3.10\"",
+]
+files = [
+ {file = "pytest_asyncio-1.1.0-py3-none-any.whl", hash = "sha256:5fe2d69607b0bd75c656d1211f969cadba035030156745ee09e7d71740e58ecf"},
+ {file = "pytest_asyncio-1.1.0.tar.gz", hash = "sha256:796aa822981e01b68c12e4827b8697108f7205020f24b5793b3c41555dab68ea"},
+]
+
+[[package]]
+name = "python-bidi"
+version = "0.6.6"
+summary = "Python Bidi layout wrapping the Rust crate unicode-bidi"
+groups = ["default"]
+files = [
+ {file = "python_bidi-0.6.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:166060a31c10aa3ffadd52cf10a3c9c2b8d78d844e0f2c5801e2ed511d3ec316"},
+ {file = "python_bidi-0.6.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8706addd827840c2c3b3a9963060d9b979b43801cc9be982efa9644facd3ed26"},
+ {file = "python_bidi-0.6.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69c02316a4f72a168ea6f66b90d845086e2f2d2de6b08eb32c576db36582177c"},
+ {file = "python_bidi-0.6.6-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a525bcb77b8edbfdcf8b199dbed24556e6d1436af8f5fa392f6cdc93ed79b4af"},
+ {file = "python_bidi-0.6.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb186c8da4bdc953893504bba93f41d5b412fd767ba5661ff606f22950ec609"},
+ {file = "python_bidi-0.6.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fa21b46dc80ac7099d2dee424b634eb1f76b2308d518e505a626c55cdbf7b1"},
+ {file = "python_bidi-0.6.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b31f5562839e7ecea881ba337f9d39716e2e0e6b3ba395e824620ee5060050ff"},
+ {file = "python_bidi-0.6.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fb750d3d5ac028e8afd62d000928a2110dbca012fee68b1a325a38caa03dc50b"},
+ {file = "python_bidi-0.6.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8b5f648ee8e9f4ac0400f71e671934b39837d7031496e0edde867a303344d758"},
+ {file = "python_bidi-0.6.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c4c0255940e6ff98fb05f9d5de3ffcaab7b60d821d4ca072b50c4f871b036562"},
+ {file = "python_bidi-0.6.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e7e36601edda15e67527560b1c00108b0d27831260b6b251cf7c6dd110645c03"},
+ {file = "python_bidi-0.6.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:07c9f000671b187319bacebb9e98d8b75005ccd16aa41b9d4411e66813c467bb"},
+ {file = "python_bidi-0.6.6-cp312-cp312-win32.whl", hash = "sha256:57c0ca449a116c4f804422111b3345281c4e69c733c4556fa216644ec9907078"},
+ {file = "python_bidi-0.6.6-cp312-cp312-win_amd64.whl", hash = "sha256:f60afe457a37bd908fdc7b520c07620b1a7cc006e08b6e3e70474025b4f5e5c7"},
+ {file = "python_bidi-0.6.6-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:61cf12f6b7d0b9bb37838a5f045e6acbd91e838b57f0369c55319bb3969ffa4d"},
+ {file = "python_bidi-0.6.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:33bd0ba5eedf18315a1475ac0f215b5134e48011b7320aedc2fb97df31d4e5bf"},
+ {file = "python_bidi-0.6.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c9f798dd49b24bb1a9d90f065ef25c7bffa94c04c554f1fc02d0aea0a9b10b0"},
+ {file = "python_bidi-0.6.6-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:43a0409570c618d93706dc875b1d33b4adfe67144f6f2ebeb32d85d8bbdb85ed"},
+ {file = "python_bidi-0.6.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ada1aecd32773c61b16f7c9f74d9ec1b57ea433e2083e08ca387c5cd4b0ceaed"},
+ {file = "python_bidi-0.6.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:125a815f2b20313a2f6d331aa84abdd07de7d270985b056e6729390a4cda90df"},
+ {file = "python_bidi-0.6.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:183fee39bd2de787f632376bd5ba0d5f1daf6a09d3ebfaa211df25d62223e531"},
+ {file = "python_bidi-0.6.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c4e08753d32d633f5ecb5eb02624272eeffaa6d5c6f4f9ddf012637bcaabfc0a"},
+ {file = "python_bidi-0.6.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d1dcd7a82ae00b86821fce627e310791f56da90924f15877cfda844e340679de"},
+ {file = "python_bidi-0.6.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5506ba56380140b3cb3504029de014d21eb8874c5e081d88495f8775f6ed90bc"},
+ {file = "python_bidi-0.6.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:207b0a7082ec38045910d37700a0dd73c10d4ffccb22a4fd0391d7e9ce241672"},
+ {file = "python_bidi-0.6.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:686642a52acdeffb1d9a593a284d07b175c63877c596fa3ccceeb2649ced1dd8"},
+ {file = "python_bidi-0.6.6-cp313-cp313-win32.whl", hash = "sha256:485f2ee109e7aa73efc165b90a6d90da52546801413540c08b7133fe729d5e0a"},
+ {file = "python_bidi-0.6.6-cp313-cp313-win_amd64.whl", hash = "sha256:63f7a9eaec31078e7611ab958b6e18e796c05b63ca50c1f7298311dc1e15ac3e"},
+ {file = "python_bidi-0.6.6.tar.gz", hash = "sha256:07db4c7da502593bd6e39c07b3a38733704070de0cbf92a7b7277b7be8867dd9"},
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+summary = "Extensions to the standard Python datetime module"
+groups = ["default"]
+dependencies = [
+ "six>=1.5",
+]
+files = [
+ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+ {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[[package]]
+name = "python-dotenv"
+version = "1.1.1"
+requires_python = ">=3.9"
+summary = "Read key-value pairs from a .env file and set them as environment variables"
+groups = ["default"]
+files = [
+ {file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"},
+ {file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"},
+]
+
+[[package]]
+name = "python-multipart"
+version = "0.0.20"
+requires_python = ">=3.8"
+summary = "A streaming multipart parser for Python"
+groups = ["default"]
+files = [
+ {file = "python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104"},
+ {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"},
+]
+
+[[package]]
+name = "pytz"
+version = "2025.2"
+summary = "World timezone definitions, modern and historical"
+groups = ["default"]
+files = [
+ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"},
+ {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"},
+]
+
+[[package]]
+name = "pywin32"
+version = "311"
+summary = "Python for Window Extensions"
+groups = ["default"]
+marker = "sys_platform == \"win32\""
+files = [
+ {file = "pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31"},
+ {file = "pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067"},
+ {file = "pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852"},
+ {file = "pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d"},
+ {file = "pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d"},
+ {file = "pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a"},
+ {file = "pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee"},
+ {file = "pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87"},
+ {file = "pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42"},
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+requires_python = ">=3.8"
+summary = "YAML parser and emitter for Python"
+groups = ["default"]
+files = [
+ {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
+ {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
+ {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
+ {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
+ {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
+ {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
+ {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
+ {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
+ {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
+ {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
+ {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
+ {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
+ {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
+ {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
+ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
+]
+
+[[package]]
+name = "referencing"
+version = "0.36.2"
+requires_python = ">=3.9"
+summary = "JSON Referencing + Python"
+groups = ["default"]
+dependencies = [
+ "attrs>=22.2.0",
+ "rpds-py>=0.7.0",
+ "typing-extensions>=4.4.0; python_version < \"3.13\"",
+]
+files = [
+ {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"},
+ {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"},
+]
+
+[[package]]
+name = "regex"
+version = "2025.7.34"
+requires_python = ">=3.9"
+summary = "Alternative regular expression module, to replace re."
+groups = ["default"]
+files = [
+ {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50"},
+ {file = "regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f"},
+ {file = "regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130"},
+ {file = "regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46"},
+ {file = "regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4"},
+ {file = "regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0"},
+ {file = "regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b"},
+ {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01"},
+ {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77"},
+ {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da"},
+ {file = "regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282"},
+ {file = "regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588"},
+ {file = "regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62"},
+ {file = "regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176"},
+ {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5"},
+ {file = "regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd"},
+ {file = "regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b"},
+ {file = "regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad"},
+ {file = "regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59"},
+ {file = "regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415"},
+ {file = "regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f"},
+ {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1"},
+ {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c"},
+ {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a"},
+ {file = "regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0"},
+ {file = "regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1"},
+ {file = "regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997"},
+ {file = "regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f"},
+ {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a"},
+ {file = "regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435"},
+ {file = "regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac"},
+ {file = "regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72"},
+ {file = "regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e"},
+ {file = "regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751"},
+ {file = "regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4"},
+ {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98"},
+ {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7"},
+ {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47"},
+ {file = "regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e"},
+ {file = "regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb"},
+ {file = "regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae"},
+ {file = "regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64"},
+ {file = "regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a"},
+]
+
+[[package]]
+name = "requests"
+version = "2.32.4"
+requires_python = ">=3.8"
+summary = "Python HTTP for Humans."
+groups = ["default"]
+dependencies = [
+ "certifi>=2017.4.17",
+ "charset-normalizer<4,>=2",
+ "idna<4,>=2.5",
+ "urllib3<3,>=1.21.1",
+]
+files = [
+ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"},
+ {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"},
+]
+
+[[package]]
+name = "rich"
+version = "14.1.0"
+requires_python = ">=3.8.0"
+summary = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+groups = ["default"]
+dependencies = [
+ "markdown-it-py>=2.2.0",
+ "pygments<3.0.0,>=2.13.0",
+]
+files = [
+ {file = "rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f"},
+ {file = "rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8"},
+]
+
+[[package]]
+name = "rpds-py"
+version = "0.27.0"
+requires_python = ">=3.9"
+summary = "Python bindings to Rust's persistent data structures (rpds)"
+groups = ["default"]
+files = [
+ {file = "rpds_py-0.27.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:19c990fdf5acecbf0623e906ae2e09ce1c58947197f9bced6bbd7482662231c4"},
+ {file = "rpds_py-0.27.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c27a7054b5224710fcfb1a626ec3ff4f28bcb89b899148c72873b18210e446b"},
+ {file = "rpds_py-0.27.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09965b314091829b378b60607022048953e25f0b396c2b70e7c4c81bcecf932e"},
+ {file = "rpds_py-0.27.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:14f028eb47f59e9169bfdf9f7ceafd29dd64902141840633683d0bad5b04ff34"},
+ {file = "rpds_py-0.27.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6168af0be75bba990a39f9431cdfae5f0ad501f4af32ae62e8856307200517b8"},
+ {file = "rpds_py-0.27.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab47fe727c13c09d0e6f508e3a49e545008e23bf762a245b020391b621f5b726"},
+ {file = "rpds_py-0.27.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa01b3d5e3b7d97efab65bd3d88f164e289ec323a8c033c5c38e53ee25c007e"},
+ {file = "rpds_py-0.27.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:6c135708e987f46053e0a1246a206f53717f9fadfba27174a9769ad4befba5c3"},
+ {file = "rpds_py-0.27.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc327f4497b7087d06204235199daf208fd01c82d80465dc5efa4ec9df1c5b4e"},
+ {file = "rpds_py-0.27.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e57906e38583a2cba67046a09c2637e23297618dc1f3caddbc493f2be97c93f"},
+ {file = "rpds_py-0.27.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f4f69d7a4300fbf91efb1fb4916421bd57804c01ab938ab50ac9c4aa2212f03"},
+ {file = "rpds_py-0.27.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b4c4fbbcff474e1e5f38be1bf04511c03d492d42eec0babda5d03af3b5589374"},
+ {file = "rpds_py-0.27.0-cp312-cp312-win32.whl", hash = "sha256:27bac29bbbf39601b2aab474daf99dbc8e7176ca3389237a23944b17f8913d97"},
+ {file = "rpds_py-0.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:8a06aa1197ec0281eb1d7daf6073e199eb832fe591ffa329b88bae28f25f5fe5"},
+ {file = "rpds_py-0.27.0-cp312-cp312-win_arm64.whl", hash = "sha256:e14aab02258cb776a108107bd15f5b5e4a1bbaa61ef33b36693dfab6f89d54f9"},
+ {file = "rpds_py-0.27.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:443d239d02d9ae55b74015234f2cd8eb09e59fbba30bf60baeb3123ad4c6d5ff"},
+ {file = "rpds_py-0.27.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8a7acf04fda1f30f1007f3cc96d29d8cf0a53e626e4e1655fdf4eabc082d367"},
+ {file = "rpds_py-0.27.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0f92b78cfc3b74a42239fdd8c1266f4715b573204c234d2f9fc3fc7a24f185"},
+ {file = "rpds_py-0.27.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce4ed8e0c7dbc5b19352b9c2c6131dd23b95fa8698b5cdd076307a33626b72dc"},
+ {file = "rpds_py-0.27.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fde355b02934cc6b07200cc3b27ab0c15870a757d1a72fd401aa92e2ea3c6bfe"},
+ {file = "rpds_py-0.27.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13bbc4846ae4c993f07c93feb21a24d8ec637573d567a924b1001e81c8ae80f9"},
+ {file = "rpds_py-0.27.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0744661afbc4099fef7f4e604e7f1ea1be1dd7284f357924af12a705cc7d5c"},
+ {file = "rpds_py-0.27.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:069e0384a54f427bd65d7fda83b68a90606a3835901aaff42185fcd94f5a9295"},
+ {file = "rpds_py-0.27.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bc262ace5a1a7dc3e2eac2fa97b8257ae795389f688b5adf22c5db1e2431c43"},
+ {file = "rpds_py-0.27.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2fe6e18e5c8581f0361b35ae575043c7029d0a92cb3429e6e596c2cdde251432"},
+ {file = "rpds_py-0.27.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d93ebdb82363d2e7bec64eecdc3632b59e84bd270d74fe5be1659f7787052f9b"},
+ {file = "rpds_py-0.27.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0954e3a92e1d62e83a54ea7b3fdc9efa5d61acef8488a8a3d31fdafbfb00460d"},
+ {file = "rpds_py-0.27.0-cp313-cp313-win32.whl", hash = "sha256:2cff9bdd6c7b906cc562a505c04a57d92e82d37200027e8d362518df427f96cd"},
+ {file = "rpds_py-0.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc79d192fb76fc0c84f2c58672c17bbbc383fd26c3cdc29daae16ce3d927e8b2"},
+ {file = "rpds_py-0.27.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b3a5c8089eed498a3af23ce87a80805ff98f6ef8f7bdb70bd1b7dae5105f6ac"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:90fb790138c1a89a2e58c9282fe1089638401f2f3b8dddd758499041bc6e0774"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010c4843a3b92b54373e3d2291a7447d6c3fc29f591772cc2ea0e9f5c1da434b"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9ce7a9e967afc0a2af7caa0d15a3e9c1054815f73d6a8cb9225b61921b419bd"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa0bf113d15e8abdfee92aa4db86761b709a09954083afcb5bf0f952d6065fdb"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb91d252b35004a84670dfeafadb042528b19842a0080d8b53e5ec1128e8f433"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db8a6313dbac934193fc17fe7610f70cd8181c542a91382531bef5ed785e5615"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce96ab0bdfcef1b8c371ada2100767ace6804ea35aacce0aef3aeb4f3f499ca8"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:7451ede3560086abe1aa27dcdcf55cd15c96b56f543fb12e5826eee6f721f858"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:32196b5a99821476537b3f7732432d64d93a58d680a52c5e12a190ee0135d8b5"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a029be818059870664157194e46ce0e995082ac49926f1423c1f058534d2aaa9"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3841f66c1ffdc6cebce8aed64e36db71466f1dc23c0d9a5592e2a782a3042c79"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:42894616da0fc0dcb2ec08a77896c3f56e9cb2f4b66acd76fc8992c3557ceb1c"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-win32.whl", hash = "sha256:b1fef1f13c842a39a03409e30ca0bf87b39a1e2a305a9924deadb75a43105d23"},
+ {file = "rpds_py-0.27.0-cp313-cp313t-win_amd64.whl", hash = "sha256:183f5e221ba3e283cd36fdfbe311d95cd87699a083330b4f792543987167eff1"},
+ {file = "rpds_py-0.27.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:f3cd110e02c5bf17d8fb562f6c9df5c20e73029d587cf8602a2da6c5ef1e32cb"},
+ {file = "rpds_py-0.27.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d0e09cf4863c74106b5265c2c310f36146e2b445ff7b3018a56799f28f39f6f"},
+ {file = "rpds_py-0.27.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f689ab822f9b5eb6dfc69893b4b9366db1d2420f7db1f6a2adf2a9ca15ad64"},
+ {file = "rpds_py-0.27.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e36c80c49853b3ffda7aa1831bf175c13356b210c73128c861f3aa93c3cc4015"},
+ {file = "rpds_py-0.27.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6de6a7f622860af0146cb9ee148682ff4d0cea0b8fd3ad51ce4d40efb2f061d0"},
+ {file = "rpds_py-0.27.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4045e2fc4b37ec4b48e8907a5819bdd3380708c139d7cc358f03a3653abedb89"},
+ {file = "rpds_py-0.27.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da162b718b12c4219eeeeb68a5b7552fbc7aadedf2efee440f88b9c0e54b45d"},
+ {file = "rpds_py-0.27.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:0665be515767dc727ffa5f74bd2ef60b0ff85dad6bb8f50d91eaa6b5fb226f51"},
+ {file = "rpds_py-0.27.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:203f581accef67300a942e49a37d74c12ceeef4514874c7cede21b012613ca2c"},
+ {file = "rpds_py-0.27.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7873b65686a6471c0037139aa000d23fe94628e0daaa27b6e40607c90e3f5ec4"},
+ {file = "rpds_py-0.27.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:249ab91ceaa6b41abc5f19513cb95b45c6f956f6b89f1fe3d99c81255a849f9e"},
+ {file = "rpds_py-0.27.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2f184336bc1d6abfaaa1262ed42739c3789b1e3a65a29916a615307d22ffd2e"},
+ {file = "rpds_py-0.27.0-cp314-cp314-win32.whl", hash = "sha256:d3c622c39f04d5751408f5b801ecb527e6e0a471b367f420a877f7a660d583f6"},
+ {file = "rpds_py-0.27.0-cp314-cp314-win_amd64.whl", hash = "sha256:cf824aceaeffff029ccfba0da637d432ca71ab21f13e7f6f5179cd88ebc77a8a"},
+ {file = "rpds_py-0.27.0-cp314-cp314-win_arm64.whl", hash = "sha256:86aca1616922b40d8ac1b3073a1ead4255a2f13405e5700c01f7c8d29a03972d"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:341d8acb6724c0c17bdf714319c393bb27f6d23d39bc74f94221b3e59fc31828"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6b96b0b784fe5fd03beffff2b1533dc0d85e92bab8d1b2c24ef3a5dc8fac5669"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c431bfb91478d7cbe368d0a699978050d3b112d7f1d440a41e90faa325557fd"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e222a44ae9f507d0f2678ee3dd0c45ec1e930f6875d99b8459631c24058aec"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:184f0d7b342967f6cda94a07d0e1fae177d11d0b8f17d73e06e36ac02889f303"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a00c91104c173c9043bc46f7b30ee5e6d2f6b1149f11f545580f5d6fdff42c0b"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a37dd208f0d658e0487522078b1ed68cd6bce20ef4b5a915d2809b9094b410"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:92f3b3ec3e6008a1fe00b7c0946a170f161ac00645cde35e3c9a68c2475e8156"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b3db5fae5cbce2131b7420a3f83553d4d89514c03d67804ced36161fe8b6b2"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5355527adaa713ab693cbce7c1e0ec71682f599f61b128cf19d07e5c13c9b1f1"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fcc01c57ce6e70b728af02b2401c5bc853a9e14eb07deda30624374f0aebfe42"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3001013dae10f806380ba739d40dee11db1ecb91684febb8406a87c2ded23dae"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-win32.whl", hash = "sha256:0f401c369186a5743694dd9fc08cba66cf70908757552e1f714bfc5219c655b5"},
+ {file = "rpds_py-0.27.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8a1dca5507fa1337f75dcd5070218b20bc68cf8844271c923c1b79dfcbc20391"},
+ {file = "rpds_py-0.27.0.tar.gz", hash = "sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f"},
+]
+
+[[package]]
+name = "ruff"
+version = "0.12.9"
+requires_python = ">=3.7"
+summary = "An extremely fast Python linter and code formatter, written in Rust."
+groups = ["default", "dev"]
+files = [
+ {file = "ruff-0.12.9-py3-none-linux_armv6l.whl", hash = "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e"},
+ {file = "ruff-0.12.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f"},
+ {file = "ruff-0.12.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340"},
+ {file = "ruff-0.12.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb"},
+ {file = "ruff-0.12.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af"},
+ {file = "ruff-0.12.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc"},
+ {file = "ruff-0.12.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66"},
+ {file = "ruff-0.12.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7"},
+ {file = "ruff-0.12.9-py3-none-win32.whl", hash = "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93"},
+ {file = "ruff-0.12.9-py3-none-win_amd64.whl", hash = "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908"},
+ {file = "ruff-0.12.9-py3-none-win_arm64.whl", hash = "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089"},
+ {file = "ruff-0.12.9.tar.gz", hash = "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a"},
+]
+
+[[package]]
+name = "safehttpx"
+version = "0.1.6"
+requires_python = ">3.9"
+summary = "A small Python library created to help developers protect their applications from Server Side Request Forgery (SSRF) attacks."
+groups = ["default"]
+dependencies = [
+ "httpx",
+]
+files = [
+ {file = "safehttpx-0.1.6-py3-none-any.whl", hash = "sha256:407cff0b410b071623087c63dd2080c3b44dc076888d8c5823c00d1e58cb381c"},
+ {file = "safehttpx-0.1.6.tar.gz", hash = "sha256:b356bfc82cee3a24c395b94a2dbeabbed60aff1aa5fa3b5fe97c4f2456ebce42"},
+]
+
+[[package]]
+name = "safetensors"
+version = "0.6.2"
+requires_python = ">=3.9"
+summary = ""
+groups = ["default"]
+files = [
+ {file = "safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba"},
+ {file = "safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b"},
+ {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd"},
+ {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a"},
+ {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1"},
+ {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda"},
+ {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f"},
+ {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19"},
+ {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce"},
+ {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7"},
+ {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5"},
+ {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac"},
+ {file = "safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1"},
+ {file = "safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c"},
+ {file = "safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9"},
+]
+
+[[package]]
+name = "scikit-image"
+version = "0.25.2"
+requires_python = ">=3.10"
+summary = "Image processing in Python"
+groups = ["default"]
+dependencies = [
+ "imageio!=2.35.0,>=2.33",
+ "lazy-loader>=0.4",
+ "networkx>=3.0",
+ "numpy>=1.24",
+ "packaging>=21",
+ "pillow>=10.1",
+ "scipy>=1.11.4",
+ "tifffile>=2022.8.12",
+]
+files = [
+ {file = "scikit_image-0.25.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8db8dd03663112783221bf01ccfc9512d1cc50ac9b5b0fe8f4023967564719fb"},
+ {file = "scikit_image-0.25.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:483bd8cc10c3d8a7a37fae36dfa5b21e239bd4ee121d91cad1f81bba10cfb0ed"},
+ {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d1e80107bcf2bf1291acfc0bf0425dceb8890abe9f38d8e94e23497cbf7ee0d"},
+ {file = "scikit_image-0.25.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17e17eb8562660cc0d31bb55643a4da996a81944b82c54805c91b3fe66f4824"},
+ {file = "scikit_image-0.25.2-cp312-cp312-win_amd64.whl", hash = "sha256:bdd2b8c1de0849964dbc54037f36b4e9420157e67e45a8709a80d727f52c7da2"},
+ {file = "scikit_image-0.25.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7efa888130f6c548ec0439b1a7ed7295bc10105458a421e9bf739b457730b6da"},
+ {file = "scikit_image-0.25.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:dd8011efe69c3641920614d550f5505f83658fe33581e49bed86feab43a180fc"},
+ {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28182a9d3e2ce3c2e251383bdda68f8d88d9fff1a3ebe1eb61206595c9773341"},
+ {file = "scikit_image-0.25.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8abd3c805ce6944b941cfed0406d88faeb19bab3ed3d4b50187af55cf24d147"},
+ {file = "scikit_image-0.25.2-cp313-cp313-win_amd64.whl", hash = "sha256:64785a8acefee460ec49a354706db0b09d1f325674107d7fa3eadb663fb56d6f"},
+ {file = "scikit_image-0.25.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330d061bd107d12f8d68f1d611ae27b3b813b8cdb0300a71d07b1379178dd4cd"},
+ {file = "scikit_image-0.25.2.tar.gz", hash = "sha256:e5a37e6cd4d0c018a7a55b9d601357e3382826d3888c10d0213fc63bff977dde"},
+]
+
+[[package]]
+name = "scipy"
+version = "1.16.1"
+requires_python = ">=3.11"
+summary = "Fundamental algorithms for scientific computing in Python"
+groups = ["default"]
+dependencies = [
+ "numpy<2.6,>=1.25.2",
+]
+files = [
+ {file = "scipy-1.16.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81b433bbeaf35728dad619afc002db9b189e45eebe2cd676effe1fb93fef2b9c"},
+ {file = "scipy-1.16.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:886cc81fdb4c6903a3bb0464047c25a6d1016fef77bb97949817d0c0d79f9e04"},
+ {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:15240c3aac087a522b4eaedb09f0ad061753c5eebf1ea430859e5bf8640d5919"},
+ {file = "scipy-1.16.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f81a25805f3659b48126b5053d9e823d3215e4a63730b5e1671852a1705921"},
+ {file = "scipy-1.16.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6c62eea7f607f122069b9bad3f99489ddca1a5173bef8a0c75555d7488b6f725"},
+ {file = "scipy-1.16.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f965bbf3235b01c776115ab18f092a95aa74c271a52577bcb0563e85738fd618"},
+ {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f006e323874ffd0b0b816d8c6a8e7f9a73d55ab3b8c3f72b752b226d0e3ac83d"},
+ {file = "scipy-1.16.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8fd15fc5085ab4cca74cb91fe0a4263b1f32e4420761ddae531ad60934c2119"},
+ {file = "scipy-1.16.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7b8013c6c066609577d910d1a2a077021727af07b6fab0ee22c2f901f22352a"},
+ {file = "scipy-1.16.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5451606823a5e73dfa621a89948096c6528e2896e40b39248295d3a0138d594f"},
+ {file = "scipy-1.16.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:89728678c5ca5abd610aee148c199ac1afb16e19844401ca97d43dc548a354eb"},
+ {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e756d688cb03fd07de0fffad475649b03cb89bee696c98ce508b17c11a03f95c"},
+ {file = "scipy-1.16.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5aa2687b9935da3ed89c5dbed5234576589dd28d0bf7cd237501ccfbdf1ad608"},
+ {file = "scipy-1.16.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0851f6a1e537fe9399f35986897e395a1aa61c574b178c0d456be5b1a0f5ca1f"},
+ {file = "scipy-1.16.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fedc2cbd1baed37474b1924c331b97bdff611d762c196fac1a9b71e67b813b1b"},
+ {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2ef500e72f9623a6735769e4b93e9dcb158d40752cdbb077f305487e3e2d1f45"},
+ {file = "scipy-1.16.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:978d8311674b05a8f7ff2ea6c6bce5d8b45a0cb09d4c5793e0318f448613ea65"},
+ {file = "scipy-1.16.1-cp313-cp313-win_amd64.whl", hash = "sha256:81929ed0fa7a5713fcdd8b2e6f73697d3b4c4816d090dd34ff937c20fa90e8ab"},
+ {file = "scipy-1.16.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:bcc12db731858abda693cecdb3bdc9e6d4bd200213f49d224fe22df82687bdd6"},
+ {file = "scipy-1.16.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:744d977daa4becb9fc59135e75c069f8d301a87d64f88f1e602a9ecf51e77b27"},
+ {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:dc54f76ac18073bcecffb98d93f03ed6b81a92ef91b5d3b135dcc81d55a724c7"},
+ {file = "scipy-1.16.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:367d567ee9fc1e9e2047d31f39d9d6a7a04e0710c86e701e053f237d14a9b4f6"},
+ {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4cf5785e44e19dcd32a0e4807555e1e9a9b8d475c6afff3d21c3c543a6aa84f4"},
+ {file = "scipy-1.16.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3d0b80fb26d3e13a794c71d4b837e2a589d839fd574a6bbb4ee1288c213ad4a3"},
+ {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8503517c44c18d1030d666cb70aaac1cc8913608816e06742498833b128488b7"},
+ {file = "scipy-1.16.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:30cc4bb81c41831ecfd6dc450baf48ffd80ef5aed0f5cf3ea775740e80f16ecc"},
+ {file = "scipy-1.16.1-cp313-cp313t-win_amd64.whl", hash = "sha256:c24fa02f7ed23ae514460a22c57eca8f530dbfa50b1cfdbf4f37c05b5309cc39"},
+ {file = "scipy-1.16.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:796a5a9ad36fa3a782375db8f4241ab02a091308eb079746bc0f874c9b998318"},
+ {file = "scipy-1.16.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:3ea0733a2ff73fd6fdc5fecca54ee9b459f4d74f00b99aced7d9a3adb43fb1cc"},
+ {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:85764fb15a2ad994e708258bb4ed8290d1305c62a4e1ef07c414356a24fcfbf8"},
+ {file = "scipy-1.16.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:ca66d980469cb623b1759bdd6e9fd97d4e33a9fad5b33771ced24d0cb24df67e"},
+ {file = "scipy-1.16.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e7cc1ffcc230f568549fc56670bcf3df1884c30bd652c5da8138199c8c76dae0"},
+ {file = "scipy-1.16.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ddfb1e8d0b540cb4ee9c53fc3dea3186f97711248fb94b4142a1b27178d8b4b"},
+ {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4dc0e7be79e95d8ba3435d193e0d8ce372f47f774cffd882f88ea4e1e1ddc731"},
+ {file = "scipy-1.16.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f23634f9e5adb51b2a77766dac217063e764337fbc816aa8ad9aaebcd4397fd3"},
+ {file = "scipy-1.16.1-cp314-cp314-win_amd64.whl", hash = "sha256:57d75524cb1c5a374958a2eae3d84e1929bb971204cc9d52213fb8589183fc19"},
+ {file = "scipy-1.16.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:d8da7c3dd67bcd93f15618938f43ed0995982eb38973023d46d4646c4283ad65"},
+ {file = "scipy-1.16.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:cc1d2f2fd48ba1e0620554fe5bc44d3e8f5d4185c8c109c7fbdf5af2792cfad2"},
+ {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:21a611ced9275cb861bacadbada0b8c0623bc00b05b09eb97f23b370fc2ae56d"},
+ {file = "scipy-1.16.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dfbb25dffc4c3dd9371d8ab456ca81beeaf6f9e1c2119f179392f0dc1ab7695"},
+ {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f0ebb7204f063fad87fc0a0e4ff4a2ff40b2a226e4ba1b7e34bf4b79bf97cd86"},
+ {file = "scipy-1.16.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f1b9e5962656f2734c2b285a8745358ecb4e4efbadd00208c80a389227ec61ff"},
+ {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e1a106f8c023d57a2a903e771228bf5c5b27b5d692088f457acacd3b54511e4"},
+ {file = "scipy-1.16.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:709559a1db68a9abc3b2c8672c4badf1614f3b440b3ab326d86a5c0491eafae3"},
+ {file = "scipy-1.16.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c0c804d60492a0aad7f5b2bb1862f4548b990049e27e828391ff2bf6f7199998"},
+ {file = "scipy-1.16.1.tar.gz", hash = "sha256:44c76f9e8b6e8e488a586190ab38016e4ed2f8a038af7cd3defa903c0a2238b3"},
+]
+
+[[package]]
+name = "semantic-version"
+version = "2.10.0"
+requires_python = ">=2.7"
+summary = "A library implementing the 'SemVer' scheme."
+groups = ["default"]
+files = [
+ {file = "semantic_version-2.10.0-py2.py3-none-any.whl", hash = "sha256:de78a3b8e0feda74cabc54aab2da702113e33ac9d9eb9d2389bcf1f58b7d9177"},
+ {file = "semantic_version-2.10.0.tar.gz", hash = "sha256:bdabb6d336998cbb378d4b9db3a4b56a1e3235701dc05ea2690d9a997ed5041c"},
+]
+
+[[package]]
+name = "setuptools"
+version = "80.9.0"
+requires_python = ">=3.9"
+summary = "Easily download, build, install, upgrade, and uninstall Python packages"
+groups = ["default"]
+files = [
+ {file = "setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922"},
+ {file = "setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c"},
+]
+
+[[package]]
+name = "shapely"
+version = "2.1.1"
+requires_python = ">=3.10"
+summary = "Manipulation and analysis of geometric objects"
+groups = ["default"]
+dependencies = [
+ "numpy>=1.21",
+]
+files = [
+ {file = "shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43"},
+ {file = "shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad"},
+ {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9"},
+ {file = "shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef"},
+ {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1"},
+ {file = "shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d"},
+ {file = "shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8"},
+ {file = "shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a"},
+ {file = "shapely-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3004a644d9e89e26c20286d5fdc10f41b1744c48ce910bd1867fdff963fe6c48"},
+ {file = "shapely-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1415146fa12d80a47d13cfad5310b3c8b9c2aa8c14a0c845c9d3d75e77cb54f6"},
+ {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21fcab88b7520820ec16d09d6bea68652ca13993c84dffc6129dc3607c95594c"},
+ {file = "shapely-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5ce6a5cc52c974b291237a96c08c5592e50f066871704fb5b12be2639d9026a"},
+ {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:04e4c12a45a1d70aeb266618d8cf81a2de9c4df511b63e105b90bfdfb52146de"},
+ {file = "shapely-2.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6ca74d851ca5264aae16c2b47e96735579686cb69fa93c4078070a0ec845b8d8"},
+ {file = "shapely-2.1.1-cp313-cp313-win32.whl", hash = "sha256:fd9130501bf42ffb7e0695b9ea17a27ae8ce68d50b56b6941c7f9b3d3453bc52"},
+ {file = "shapely-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:ab8d878687b438a2f4c138ed1a80941c6ab0029e0f4c785ecfe114413b498a97"},
+ {file = "shapely-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c062384316a47f776305ed2fa22182717508ffdeb4a56d0ff4087a77b2a0f6d"},
+ {file = "shapely-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4ecf6c196b896e8f1360cc219ed4eee1c1e5f5883e505d449f263bd053fb8c05"},
+ {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb00070b4c4860f6743c600285109c273cca5241e970ad56bb87bef0be1ea3a0"},
+ {file = "shapely-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d14a9afa5fa980fbe7bf63706fdfb8ff588f638f145a1d9dbc18374b5b7de913"},
+ {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b640e390dabde790e3fb947198b466e63223e0a9ccd787da5f07bcb14756c28d"},
+ {file = "shapely-2.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:69e08bf9697c1b73ec6aa70437db922bafcea7baca131c90c26d59491a9760f9"},
+ {file = "shapely-2.1.1-cp313-cp313t-win32.whl", hash = "sha256:ef2d09d5a964cc90c2c18b03566cf918a61c248596998a0301d5b632beadb9db"},
+ {file = "shapely-2.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:8cb8f17c377260452e9d7720eeaf59082c5f8ea48cf104524d953e5d36d4bdb7"},
+ {file = "shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772"},
+]
+
+[[package]]
+name = "shellingham"
+version = "1.5.4"
+requires_python = ">=3.7"
+summary = "Tool to Detect Surrounding Shell"
+groups = ["default"]
+marker = "sys_platform != \"emscripten\""
+files = [
+ {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
+ {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
+]
+
+[[package]]
+name = "six"
+version = "1.17.0"
+requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+summary = "Python 2 and 3 compatibility utilities"
+groups = ["default"]
+files = [
+ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
+ {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+requires_python = ">=3.7"
+summary = "Sniff out which async library your code is running under"
+groups = ["default"]
+files = [
+ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
+]
+
+[[package]]
+name = "soundfile"
+version = "0.13.1"
+summary = "An audio library based on libsndfile, CFFI and NumPy"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+dependencies = [
+ "cffi>=1.0",
+ "numpy",
+]
+files = [
+ {file = "soundfile-0.13.1-py2.py3-none-any.whl", hash = "sha256:a23c717560da2cf4c7b5ae1142514e0fd82d6bbd9dfc93a50423447142f2c445"},
+ {file = "soundfile-0.13.1-py2.py3-none-macosx_10_9_x86_64.whl", hash = "sha256:82dc664d19831933fe59adad199bf3945ad06d84bc111a5b4c0d3089a5b9ec33"},
+ {file = "soundfile-0.13.1-py2.py3-none-macosx_11_0_arm64.whl", hash = "sha256:743f12c12c4054921e15736c6be09ac26b3b3d603aef6fd69f9dde68748f2593"},
+ {file = "soundfile-0.13.1-py2.py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:9c9e855f5a4d06ce4213f31918653ab7de0c5a8d8107cd2427e44b42df547deb"},
+ {file = "soundfile-0.13.1-py2.py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:03267c4e493315294834a0870f31dbb3b28a95561b80b134f0bd3cf2d5f0e618"},
+ {file = "soundfile-0.13.1-py2.py3-none-win32.whl", hash = "sha256:c734564fab7c5ddf8e9be5bf70bab68042cd17e9c214c06e365e20d64f9a69d5"},
+ {file = "soundfile-0.13.1-py2.py3-none-win_amd64.whl", hash = "sha256:1e70a05a0626524a69e9f0f4dd2ec174b4e9567f4d8b6c11d38b5c289be36ee9"},
+ {file = "soundfile-0.13.1.tar.gz", hash = "sha256:b2c68dab1e30297317080a5b43df57e302584c49e2942defdde0acccc53f0e5b"},
+]
+
+[[package]]
+name = "starlette"
+version = "0.47.2"
+requires_python = ">=3.9"
+summary = "The little ASGI library that shines."
+groups = ["default"]
+dependencies = [
+ "anyio<5,>=3.6.2",
+ "typing-extensions>=4.10.0; python_version < \"3.13\"",
+]
+files = [
+ {file = "starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b"},
+ {file = "starlette-0.47.2.tar.gz", hash = "sha256:6ae9aa5db235e4846decc1e7b79c4f346adf41e9777aebeb49dfd09bbd7023d8"},
+]
+
+[[package]]
+name = "supervision"
+version = "0.26.1"
+requires_python = ">=3.9"
+summary = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
+groups = ["default"]
+dependencies = [
+ "defusedxml>=0.7.1",
+ "matplotlib>=3.6.0",
+ "numpy>=1.21.2",
+ "opencv-python>=4.5.5.64",
+ "pillow>=9.4",
+ "pyyaml>=5.3",
+ "requests>=2.26.0",
+ "scipy>=1.10.0",
+ "tqdm>=4.62.3",
+]
+files = [
+ {file = "supervision-0.26.1-py3-none-any.whl", hash = "sha256:43c55e2830f38f5750be7266208992dc16996da9c9478e067bc2617ebaf91c1a"},
+ {file = "supervision-0.26.1.tar.gz", hash = "sha256:af0db9c5459bb640cf0d31e9a4df3296020b4cd0dd484d8659eafe7b475b68f2"},
+]
+
+[[package]]
+name = "sympy"
+version = "1.14.0"
+requires_python = ">=3.9"
+summary = "Computer algebra system (CAS) in Python"
+groups = ["default"]
+dependencies = [
+ "mpmath<1.4,>=1.1.0",
+]
+files = [
+ {file = "sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5"},
+ {file = "sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517"},
+]
+
+[[package]]
+name = "termcolor"
+version = "2.3.0"
+requires_python = ">=3.7"
+summary = "ANSI color formatting for output in terminal"
+groups = ["default"]
+files = [
+ {file = "termcolor-2.3.0-py3-none-any.whl", hash = "sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475"},
+ {file = "termcolor-2.3.0.tar.gz", hash = "sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a"},
+]
+
+[[package]]
+name = "tifffile"
+version = "2025.6.11"
+requires_python = ">=3.11"
+summary = "Read and write TIFF files"
+groups = ["default"]
+dependencies = [
+ "numpy",
+]
+files = [
+ {file = "tifffile-2025.6.11-py3-none-any.whl", hash = "sha256:32effb78b10b3a283eb92d4ebf844ae7e93e151458b0412f38518b4e6d2d7542"},
+ {file = "tifffile-2025.6.11.tar.gz", hash = "sha256:0ece4c2e7a10656957d568a093b07513c0728d30c1bd8cc12725901fffdb7143"},
+]
+
+[[package]]
+name = "tiktoken"
+version = "0.11.0"
+requires_python = ">=3.9"
+summary = "tiktoken is a fast BPE tokeniser for use with OpenAI's models"
+groups = ["default"]
+dependencies = [
+ "regex>=2022.1.18",
+ "requests>=2.26.0",
+]
+files = [
+ {file = "tiktoken-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fd9e6b23e860973cf9526544e220b223c60badf5b62e80a33509d6d40e6c8f5d"},
+ {file = "tiktoken-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76d53cee2da71ee2731c9caa747398762bda19d7f92665e882fef229cb0b5b"},
+ {file = "tiktoken-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef72aab3ea240646e642413cb363b73869fed4e604dcfd69eec63dc54d603e8"},
+ {file = "tiktoken-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f929255c705efec7a28bf515e29dc74220b2f07544a8c81b8d69e8efc4578bd"},
+ {file = "tiktoken-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61f1d15822e4404953d499fd1dcc62817a12ae9fb1e4898033ec8fe3915fdf8e"},
+ {file = "tiktoken-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:45927a71ab6643dfd3ef57d515a5db3d199137adf551f66453be098502838b0f"},
+ {file = "tiktoken-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a5f3f25ffb152ee7fec78e90a5e5ea5b03b4ea240beed03305615847f7a6ace2"},
+ {file = "tiktoken-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dc6e9ad16a2a75b4c4be7208055a1f707c9510541d94d9cc31f7fbdc8db41d8"},
+ {file = "tiktoken-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a0517634d67a8a48fd4a4ad73930c3022629a85a217d256a6e9b8b47439d1e4"},
+ {file = "tiktoken-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fb4effe60574675118b73c6fbfd3b5868e5d7a1f570d6cc0d18724b09ecf318"},
+ {file = "tiktoken-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94f984c9831fd32688aef4348803b0905d4ae9c432303087bae370dc1381a2b8"},
+ {file = "tiktoken-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2177ffda31dec4023356a441793fed82f7af5291120751dee4d696414f54db0c"},
+ {file = "tiktoken-0.11.0.tar.gz", hash = "sha256:3c518641aee1c52247c2b97e74d8d07d780092af79d5911a6ab5e79359d9b06a"},
+]
+
+[[package]]
+name = "tokenizers"
+version = "0.21.4"
+requires_python = ">=3.9"
+summary = ""
+groups = ["default"]
+dependencies = [
+ "huggingface-hub<1.0,>=0.16.4",
+]
+files = [
+ {file = "tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133"},
+ {file = "tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60"},
+ {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5"},
+ {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6"},
+ {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9"},
+ {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732"},
+ {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2"},
+ {file = "tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff"},
+ {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2"},
+ {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78"},
+ {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b"},
+ {file = "tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24"},
+ {file = "tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0"},
+ {file = "tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597"},
+ {file = "tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880"},
+]
+
+[[package]]
+name = "tomlkit"
+version = "0.13.3"
+requires_python = ">=3.8"
+summary = "Style preserving TOML library"
+groups = ["default"]
+files = [
+ {file = "tomlkit-0.13.3-py3-none-any.whl", hash = "sha256:c89c649d79ee40629a9fda55f8ace8c6a1b42deb912b2a8fd8d942ddadb606b0"},
+ {file = "tomlkit-0.13.3.tar.gz", hash = "sha256:430cf247ee57df2b94ee3fbe588e71d362a941ebb545dec29b53961d61add2a1"},
+]
+
+[[package]]
+name = "torch"
+version = "2.7.1"
+requires_python = ">=3.9.0"
+summary = "Tensors and Dynamic neural networks in Python with strong GPU acceleration"
+groups = ["default"]
+dependencies = [
+ "filelock",
+ "fsspec",
+ "jinja2",
+ "networkx",
+ "nvidia-cublas-cu12==12.6.4.1; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cuda-cupti-cu12==12.6.80; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cuda-nvrtc-cu12==12.6.77; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cuda-runtime-cu12==12.6.77; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cudnn-cu12==9.5.1.17; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cufft-cu12==11.3.0.4; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cufile-cu12==1.11.1.6; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-curand-cu12==10.3.7.77; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cusolver-cu12==11.7.1.2; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cusparse-cu12==12.5.4.2; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-cusparselt-cu12==0.6.3; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-nccl-cu12==2.26.2; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-nvjitlink-cu12==12.6.85; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "nvidia-nvtx-cu12==12.6.77; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "setuptools; python_version >= \"3.12\"",
+ "sympy>=1.13.3",
+ "triton==3.3.1; platform_system == \"Linux\" and platform_machine == \"x86_64\"",
+ "typing-extensions>=4.10.0",
+]
+files = [
+ {file = "torch-2.7.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ea1e518df4c9de73af7e8a720770f3628e7f667280bce2be7a16292697e3fa"},
+ {file = "torch-2.7.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c33360cfc2edd976c2633b3b66c769bdcbbf0e0b6550606d188431c81e7dd1fc"},
+ {file = "torch-2.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d8bf6e1856ddd1807e79dc57e54d3335f2b62e6f316ed13ed3ecfe1fc1df3d8b"},
+ {file = "torch-2.7.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:787687087412c4bd68d315e39bc1223f08aae1d16a9e9771d95eabbb04ae98fb"},
+ {file = "torch-2.7.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:03563603d931e70722dce0e11999d53aa80a375a3d78e6b39b9f6805ea0a8d28"},
+ {file = "torch-2.7.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:d632f5417b6980f61404a125b999ca6ebd0b8b4bbdbb5fbbba44374ab619a412"},
+ {file = "torch-2.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:23660443e13995ee93e3d844786701ea4ca69f337027b05182f5ba053ce43b38"},
+ {file = "torch-2.7.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:0da4f4dba9f65d0d203794e619fe7ca3247a55ffdcbd17ae8fb83c8b2dc9b585"},
+ {file = "torch-2.7.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:e08d7e6f21a617fe38eeb46dd2213ded43f27c072e9165dc27300c9ef9570934"},
+ {file = "torch-2.7.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:30207f672328a42df4f2174b8f426f354b2baa0b7cca3a0adb3d6ab5daf00dc8"},
+ {file = "torch-2.7.1-cp313-cp313t-win_amd64.whl", hash = "sha256:79042feca1c634aaf6603fe6feea8c6b30dfa140a6bbc0b973e2260c7e79a22e"},
+ {file = "torch-2.7.1-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:988b0cbc4333618a1056d2ebad9eb10089637b659eb645434d0809d8d937b946"},
+]
+
+[[package]]
+name = "torchvision"
+version = "0.22.1"
+requires_python = ">=3.9"
+summary = "image and video datasets and models for torch deep learning"
+groups = ["default"]
+dependencies = [
+ "numpy",
+ "pillow!=8.3.*,>=5.3.0",
+ "torch==2.7.1",
+]
+files = [
+ {file = "torchvision-0.22.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:153f1790e505bd6da123e21eee6e83e2e155df05c0fe7d56347303067d8543c5"},
+ {file = "torchvision-0.22.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:964414eef19459d55a10e886e2fca50677550e243586d1678f65e3f6f6bac47a"},
+ {file = "torchvision-0.22.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:699c2d70d33951187f6ed910ea05720b9b4aaac1dcc1135f53162ce7d42481d3"},
+ {file = "torchvision-0.22.1-cp312-cp312-win_amd64.whl", hash = "sha256:75e0897da7a8e43d78632f66f2bdc4f6e26da8d3f021a7c0fa83746073c2597b"},
+ {file = "torchvision-0.22.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9c3ae3319624c43cc8127020f46c14aa878406781f0899bb6283ae474afeafbf"},
+ {file = "torchvision-0.22.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:4a614a6a408d2ed74208d0ea6c28a2fbb68290e9a7df206c5fef3f0b6865d307"},
+ {file = "torchvision-0.22.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:7ee682be589bb1a002b7704f06b8ec0b89e4b9068f48e79307d2c6e937a9fdf4"},
+ {file = "torchvision-0.22.1-cp313-cp313-win_amd64.whl", hash = "sha256:2566cafcfa47ecfdbeed04bab8cef1307c8d4ef75046f7624b9e55f384880dfe"},
+ {file = "torchvision-0.22.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:043d9e35ed69c2e586aff6eb9e2887382e7863707115668ac9d140da58f42cba"},
+ {file = "torchvision-0.22.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:27142bcc8a984227a6dcf560985e83f52b82a7d3f5fe9051af586a2ccc46ef26"},
+ {file = "torchvision-0.22.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ef46e065502f7300ad6abc98554131c35dc4c837b978d91306658f1a65c00baa"},
+ {file = "torchvision-0.22.1-cp313-cp313t-win_amd64.whl", hash = "sha256:7414eeacfb941fa21acddcd725f1617da5630ec822e498660a4b864d7d998075"},
+]
+
+[[package]]
+name = "tqdm"
+version = "4.67.1"
+requires_python = ">=3.7"
+summary = "Fast, Extensible Progress Meter"
+groups = ["default"]
+dependencies = [
+ "colorama; platform_system == \"Windows\"",
+]
+files = [
+ {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"},
+ {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"},
+]
+
+[[package]]
+name = "transformers"
+version = "4.55.2"
+requires_python = ">=3.9.0"
+summary = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow"
+groups = ["default"]
+dependencies = [
+ "filelock",
+ "huggingface-hub<1.0,>=0.34.0",
+ "numpy>=1.17",
+ "packaging>=20.0",
+ "pyyaml>=5.1",
+ "regex!=2019.12.17",
+ "requests",
+ "safetensors>=0.4.3",
+ "tokenizers<0.22,>=0.21",
+ "tqdm>=4.27",
+]
+files = [
+ {file = "transformers-4.55.2-py3-none-any.whl", hash = "sha256:097e3c2e2c0c9681db3da9d748d8f9d6a724c644514673d0030e8c5a1109f1f1"},
+ {file = "transformers-4.55.2.tar.gz", hash = "sha256:a45ec60c03474fd67adbce5c434685051b7608b3f4f167c25aa6aeb1cad16d4f"},
+]
+
+[[package]]
+name = "triton"
+version = "3.3.1"
+summary = "A language and compiler for custom Deep Learning operations"
+groups = ["default"]
+marker = "platform_system == \"Linux\" and platform_machine == \"x86_64\""
+dependencies = [
+ "setuptools>=40.8.0",
+]
+files = [
+ {file = "triton-3.3.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9999e83aba21e1a78c1f36f21bce621b77bcaa530277a50484a7cb4a822f6e43"},
+ {file = "triton-3.3.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b89d846b5a4198317fec27a5d3a609ea96b6d557ff44b56c23176546023c4240"},
+ {file = "triton-3.3.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3198adb9d78b77818a5388bff89fa72ff36f9da0bc689db2f0a651a67ce6a42"},
+]
+
+[[package]]
+name = "typer"
+version = "0.16.0"
+requires_python = ">=3.7"
+summary = "Typer, build great CLIs. Easy to code. Based on Python type hints."
+groups = ["default"]
+marker = "sys_platform != \"emscripten\""
+dependencies = [
+ "click>=8.0.0",
+ "rich>=10.11.0",
+ "shellingham>=1.3.0",
+ "typing-extensions>=3.7.4.3",
+]
+files = [
+ {file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"},
+ {file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"},
+]
+
+[[package]]
+name = "types-aiofiles"
+version = "24.1.0.20250809"
+requires_python = ">=3.9"
+summary = "Typing stubs for aiofiles"
+groups = ["dev"]
+files = [
+ {file = "types_aiofiles-24.1.0.20250809-py3-none-any.whl", hash = "sha256:657c83f876047ffc242b34bfcd9167f201d1b02e914ee854f16e589aa95c0d45"},
+ {file = "types_aiofiles-24.1.0.20250809.tar.gz", hash = "sha256:4dc9734330b1324d9251f92edfc94fd6827fbb829c593313f034a77ac33ae327"},
+]
+
+[[package]]
+name = "typing-extensions"
+version = "4.14.1"
+requires_python = ">=3.9"
+summary = "Backported and Experimental Type Hints for Python 3.9+"
+groups = ["default", "dev"]
+files = [
+ {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"},
+ {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"},
+]
+
+[[package]]
+name = "typing-inspection"
+version = "0.4.1"
+requires_python = ">=3.9"
+summary = "Runtime typing introspection tools"
+groups = ["default"]
+dependencies = [
+ "typing-extensions>=4.12.0",
+]
+files = [
+ {file = "typing_inspection-0.4.1-py3-none-any.whl", hash = "sha256:389055682238f53b04f7badcb49b989835495a96700ced5dab2d8feae4b26f51"},
+ {file = "typing_inspection-0.4.1.tar.gz", hash = "sha256:6ae134cc0203c33377d43188d4064e9b357dba58cff3185f22924610e70a9d28"},
+]
+
+[[package]]
+name = "tzdata"
+version = "2025.2"
+requires_python = ">=2"
+summary = "Provider of IANA time zone data"
+groups = ["default"]
+files = [
+ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"},
+ {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"},
+]
+
+[[package]]
+name = "ultralytics"
+version = "8.3.179"
+requires_python = ">=3.8"
+summary = "Ultralytics YOLO 🚀 for SOTA object detection, multi-object tracking, instance segmentation, pose estimation and image classification."
+groups = ["default"]
+dependencies = [
+ "matplotlib>=3.3.0",
+ "numpy>=1.23.0",
+ "opencv-python>=4.6.0",
+ "pandas>=1.1.4",
+ "pillow>=7.1.2",
+ "psutil",
+ "py-cpuinfo",
+ "pyyaml>=5.3.1",
+ "requests>=2.23.0",
+ "scipy>=1.4.1",
+ "torch!=2.4.0,>=1.8.0; sys_platform == \"win32\"",
+ "torch>=1.8.0",
+ "torchvision>=0.9.0",
+ "tqdm>=4.64.0",
+ "ultralytics-thop>=2.0.0",
+]
+files = [
+ {file = "ultralytics-8.3.179-py3-none-any.whl", hash = "sha256:521fbb7853edb2fd0f74583a95e7f40be6c7c3e7cb13abe2892bf83e423a82a3"},
+ {file = "ultralytics-8.3.179.tar.gz", hash = "sha256:bf0a617c534aa9ac334c4c8a7ae9f44618295f78f1588707528d27e92ec35376"},
+]
+
+[[package]]
+name = "ultralytics-thop"
+version = "2.0.15"
+requires_python = ">=3.8"
+summary = "Ultralytics THOP package for fast computation of PyTorch model FLOPs and parameters."
+groups = ["default"]
+dependencies = [
+ "numpy",
+ "torch",
+]
+files = [
+ {file = "ultralytics_thop-2.0.15-py3-none-any.whl", hash = "sha256:d643e074754c154a4cb8e97190f00ce57c999b8b6e756d780d730dc3a1e51ef6"},
+ {file = "ultralytics_thop-2.0.15.tar.gz", hash = "sha256:1bcf05dc0172045ce3da289f9125b36f999f5225596cb69fe12e09e139896e41"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.5.0"
+requires_python = ">=3.9"
+summary = "HTTP library with thread-safe connection pooling, file post, and more."
+groups = ["default"]
+files = [
+ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
+ {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
+]
+
+[[package]]
+name = "uvicorn"
+version = "0.35.0"
+requires_python = ">=3.9"
+summary = "The lightning-fast ASGI server."
+groups = ["default"]
+marker = "sys_platform != \"emscripten\""
+dependencies = [
+ "click>=7.0",
+ "h11>=0.8",
+ "typing-extensions>=4.0; python_version < \"3.11\"",
+]
+files = [
+ {file = "uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a"},
+ {file = "uvicorn-0.35.0.tar.gz", hash = "sha256:bc662f087f7cf2ce11a1d7fd70b90c9f98ef2e2831556dd078d131b96cc94a01"},
+]
+
+[[package]]
+name = "websocket-client"
+version = "1.8.0"
+requires_python = ">=3.8"
+summary = "WebSocket client for Python with low level API options"
+groups = ["default"]
+files = [
+ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"},
+ {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"},
+]
+
+[[package]]
+name = "websockets"
+version = "15.0.1"
+requires_python = ">=3.9"
+summary = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+groups = ["default"]
+files = [
+ {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"},
+ {file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"},
+ {file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"},
+ {file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"},
+ {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"},
+ {file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"},
+ {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"},
+ {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"},
+ {file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"},
+ {file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"},
+ {file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"},
+ {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"},
+ {file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"},
+ {file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"},
+ {file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"},
+ {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"},
+ {file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"},
+ {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"},
+ {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"},
+ {file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"},
+ {file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"},
+ {file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"},
+ {file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"},
+ {file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"},
+]
+
+[[package]]
+name = "xxhash"
+version = "3.5.0"
+requires_python = ">=3.7"
+summary = "Python binding for xxHash"
+groups = ["default"]
+marker = "sys_platform == \"darwin\""
+files = [
+ {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"},
+ {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"},
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"},
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"},
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"},
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"},
+ {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"},
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"},
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"},
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"},
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"},
+ {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"},
+ {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"},
+ {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"},
+ {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"},
+ {file = "xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6"},
+ {file = "xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5"},
+ {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc"},
+ {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3"},
+ {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c"},
+ {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb"},
+ {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f"},
+ {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7"},
+ {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326"},
+ {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf"},
+ {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7"},
+ {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c"},
+ {file = "xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637"},
+ {file = "xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43"},
+ {file = "xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b"},
+ {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"},
+]
+
+[[package]]
+name = "yarl"
+version = "1.20.1"
+requires_python = ">=3.9"
+summary = "Yet another URL library"
+groups = ["default"]
+dependencies = [
+ "idna>=2.0",
+ "multidict>=4.0",
+ "propcache>=0.2.1",
+]
+files = [
+ {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9"},
+ {file = "yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a"},
+ {file = "yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd"},
+ {file = "yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a"},
+ {file = "yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004"},
+ {file = "yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5"},
+ {file = "yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698"},
+ {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:0b5ff0fbb7c9f1b1b5ab53330acbfc5247893069e7716840c8e7d5bb7355038a"},
+ {file = "yarl-1.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:14f326acd845c2b2e2eb38fb1346c94f7f3b01a4f5c788f8144f9b630bfff9a3"},
+ {file = "yarl-1.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f60e4ad5db23f0b96e49c018596707c3ae89f5d0bd97f0ad3684bcbad899f1e7"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:49bdd1b8e00ce57e68ba51916e4bb04461746e794e7c4d4bbc42ba2f18297691"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:66252d780b45189975abfed839616e8fd2dbacbdc262105ad7742c6ae58f3e31"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59174e7332f5d153d8f7452a102b103e2e74035ad085f404df2e40e663a22b28"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e3968ec7d92a0c0f9ac34d5ecfd03869ec0cab0697c91a45db3fbbd95fe1b653"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1a4fbb50e14396ba3d375f68bfe02215d8e7bc3ec49da8341fe3157f59d2ff5"},
+ {file = "yarl-1.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11a62c839c3a8eac2410e951301309426f368388ff2f33799052787035793b02"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:041eaa14f73ff5a8986b4388ac6bb43a77f2ea09bf1913df7a35d4646db69e53"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:377fae2fef158e8fd9d60b4c8751387b8d1fb121d3d0b8e9b0be07d1b41e83dc"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1c92f4390e407513f619d49319023664643d3339bd5e5a56a3bebe01bc67ec04"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d25ddcf954df1754ab0f86bb696af765c5bfaba39b74095f27eececa049ef9a4"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:909313577e9619dcff8c31a0ea2aa0a2a828341d92673015456b3ae492e7317b"},
+ {file = "yarl-1.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:793fd0580cb9664548c6b83c63b43c477212c0260891ddf86809e1c06c8b08f1"},
+ {file = "yarl-1.20.1-cp313-cp313-win32.whl", hash = "sha256:468f6e40285de5a5b3c44981ca3a319a4b208ccc07d526b20b12aeedcfa654b7"},
+ {file = "yarl-1.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:495b4ef2fea40596bfc0affe3837411d6aa3371abcf31aac0ccc4bdd64d4ef5c"},
+ {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f60233b98423aab21d249a30eb27c389c14929f47be8430efa7dbd91493a729d"},
+ {file = "yarl-1.20.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:6f3eff4cc3f03d650d8755c6eefc844edde99d641d0dcf4da3ab27141a5f8ddf"},
+ {file = "yarl-1.20.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:69ff8439d8ba832d6bed88af2c2b3445977eba9a4588b787b32945871c2444e3"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf34efa60eb81dd2645a2e13e00bb98b76c35ab5061a3989c7a70f78c85006d"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8e0fe9364ad0fddab2688ce72cb7a8e61ea42eff3c7caeeb83874a5d479c896c"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f64fbf81878ba914562c672024089e3401974a39767747691c65080a67b18c1"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6342d643bf9a1de97e512e45e4b9560a043347e779a173250824f8b254bd5ce"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56dac5f452ed25eef0f6e3c6a066c6ab68971d96a9fb441791cad0efba6140d3"},
+ {file = "yarl-1.20.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7d7f497126d65e2cad8dc5f97d34c27b19199b6414a40cb36b52f41b79014be"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:67e708dfb8e78d8a19169818eeb5c7a80717562de9051bf2413aca8e3696bf16"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:595c07bc79af2494365cc96ddeb772f76272364ef7c80fb892ef9d0649586513"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7bdd2f80f4a7df852ab9ab49484a4dee8030023aa536df41f2d922fd57bf023f"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:c03bfebc4ae8d862f853a9757199677ab74ec25424d0ebd68a0027e9c639a390"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:344d1103e9c1523f32a5ed704d576172d2cabed3122ea90b1d4e11fe17c66458"},
+ {file = "yarl-1.20.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:88cab98aa4e13e1ade8c141daeedd300a4603b7132819c484841bb7af3edce9e"},
+ {file = "yarl-1.20.1-cp313-cp313t-win32.whl", hash = "sha256:b121ff6a7cbd4abc28985b6028235491941b9fe8fe226e6fdc539c977ea1739d"},
+ {file = "yarl-1.20.1-cp313-cp313t-win_amd64.whl", hash = "sha256:541d050a355bbbc27e55d906bc91cb6fe42f96c01413dd0f4ed5a5240513874f"},
+ {file = "yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77"},
+ {file = "yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac"},
+]
+
+[[package]]
+name = "yaspin"
+version = "3.1.0"
+requires_python = "<4.0,>=3.9"
+summary = "Yet Another Terminal Spinner"
+groups = ["default"]
+dependencies = [
+ "termcolor<2.4.0,>=2.2.0",
+]
+files = [
+ {file = "yaspin-3.1.0-py3-none-any.whl", hash = "sha256:5e3d4dfb547d942cae6565718123f1ecfa93e745b7e51871ad2bbae839e71b73"},
+ {file = "yaspin-3.1.0.tar.gz", hash = "sha256:7b97c7e257ec598f98cef9878e038bfa619ceb54ac31d61d8ead2b3128f8d7c7"},
+]
+
+[[package]]
+name = "zipp"
+version = "3.23.0"
+requires_python = ">=3.9"
+summary = "Backport of pathlib-compatible object wrapper for zip files"
+groups = ["default"]
+files = [
+ {file = "zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e"},
+ {file = "zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166"},
+]
diff --git a/libs/python/snapshot-manager/pyproject.toml b/libs/python/snapshot-manager/pyproject.toml
new file mode 100644
index 00000000..714809b4
--- /dev/null
+++ b/libs/python/snapshot-manager/pyproject.toml
@@ -0,0 +1,59 @@
+[build-system]
+requires = ["setuptools>=61.0", "wheel"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "cua-snapshot-manager"
+version = "0.1.0"
+description = "Snapshot-based state management system for the Cua Agent SDK"
+authors = [
+ {name = "Daniele", email = "daniele@example.com"}
+]
+license = {text = "MIT"}
+readme = "README.md"
+requires-python = ">=3.12"
+dependencies = [
+ "docker>=6.0.0",
+ "pydantic>=2.0.0",
+ "click>=8.0.0",
+ "aiofiles>=23.0.0",
+ "cua-agent[all]>=0.2.5",
+ "cua-computer>=0.2.6",
+ "python-dotenv>=1.1.1",
+]
+
+[project.optional-dependencies]
+dev = [
+ "pytest>=7.0.0",
+ "pytest-asyncio>=0.21.0",
+ "black>=23.0.0",
+ "ruff>=0.1.0",
+ "mypy>=1.17.1",
+ "types-aiofiles>=24.1.0.20250809",
+]
+
+[project.scripts]
+snapshot-manager = "snapshot_manager.cli:main"
+
+[tool.setuptools.packages.find]
+where = ["src"]
+
+[tool.setuptools.package-dir]
+"" = "src"
+
+[tool.black]
+line-length = 100
+target-version = ['py38']
+
+[tool.ruff]
+line-length = 100
+target-version = "py38"
+
+[dependency-groups]
+dev = [
+ "black>=24.8.0",
+ "isort>=5.13.2",
+ "pytest>=8.4.1",
+ "pytest-asyncio>=1.1.0",
+ "ruff>=0.12.9",
+]
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/__init__.py b/libs/python/snapshot-manager/src/snapshot_manager/__init__.py
new file mode 100644
index 00000000..8b2d309d
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/__init__.py
@@ -0,0 +1,24 @@
+"""
+Snapshot-based state management system for the Cua Agent SDK.
+
+This package provides functionality to capture, store, and restore container
+snapshots at defined intervals or events during agent execution.
+"""
+
+from .callback import SnapshotCallback
+from .manager import SnapshotManager
+from .models import SnapshotConfig, SnapshotMetadata, SnapshotTrigger
+from .providers import DockerSnapshotProvider
+from .storage import FileSystemSnapshotStorage
+
+__version__ = "0.1.0"
+
+__all__ = [
+ "SnapshotMetadata",
+ "SnapshotTrigger",
+ "SnapshotConfig",
+ "SnapshotManager",
+ "SnapshotCallback",
+ "DockerSnapshotProvider",
+ "FileSystemSnapshotStorage",
+]
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/callback.py b/libs/python/snapshot-manager/src/snapshot_manager/callback.py
new file mode 100644
index 00000000..0dbeb2eb
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/callback.py
@@ -0,0 +1,365 @@
+"""
+CUA Agent SDK callback integration for automatic snapshot management.
+
+This callback handler integrates with the CUA Agent SDK's callback system
+to automatically create snapshots at appropriate lifecycle events.
+"""
+
+import logging
+from typing import Dict, List, Any, Optional, Callable
+
+try:
+ from agent.callbacks.base import AsyncCallbackHandler
+
+ HAVE_CUA_SDK = True
+except ImportError:
+ # Fallback for when CUA SDK is not available
+ class AsyncCallbackHandler:
+ pass
+
+ HAVE_CUA_SDK = False
+
+from .manager import SnapshotManager
+from .models import SnapshotTrigger, SnapshotConfig
+
+logger = logging.getLogger(__name__)
+
+
+class SnapshotCallback(AsyncCallbackHandler):
+ """
+ CUA Agent SDK callback handler for snapshot management.
+
+ This callback integrates with the agent's lifecycle to automatically
+ create snapshots when configured events occur. It's designed to be
+ pluggable and non-intrusive to existing agent workflows.
+ """
+
+ def __init__(
+ self,
+ snapshot_manager: SnapshotManager,
+ config: Optional[SnapshotConfig] = None,
+ container_resolver: Optional[Callable[[Dict[str, Any]], Optional[str]]] = None,
+ ):
+ """
+ Initialize the snapshot callback.
+
+ Args:
+ snapshot_manager: SnapshotManager instance with explicit provider/storage wiring
+ config: Configuration for snapshot behavior (uses manager's config if None)
+ container_resolver: Function to resolve container ID from agent context
+ """
+ if snapshot_manager is None:
+ raise ValueError(
+ "SnapshotManager must be explicitly provided with proper "
+ "DockerSnapshotProvider and storage configuration"
+ )
+
+ self.snapshot_manager = snapshot_manager
+ self.config = config or snapshot_manager.config
+
+ # Function to resolve container ID from agent context
+ # Default implementation assumes container info is in kwargs
+ self.container_resolver = container_resolver or self._default_container_resolver
+
+ # Track current run state
+ self._current_run_id: Optional[str] = None
+ self._current_container_id: Optional[str] = None
+ self._action_count = 0
+
+ logger.info("SnapshotCallback initialized")
+
+ def _default_container_resolver(self, kwargs: Dict[str, Any]) -> Optional[str]:
+ """
+ Default container ID resolver.
+
+ Args:
+ kwargs: Agent run arguments
+
+ Returns:
+ Container ID if found, None otherwise
+ """
+ # Look for container information in various common locations
+ # This is a best-effort implementation that may need customization
+
+ # Check for direct container_id parameter
+ if "container_id" in kwargs:
+ return kwargs["container_id"]
+
+ # Check for computer/tool objects that might have container info
+ tools = kwargs.get("tools", [])
+ for tool in tools:
+ if hasattr(tool, "container_id"):
+ return tool.container_id
+ elif hasattr(tool, "name") and hasattr(tool, "attrs"):
+ # Possible Computer object
+ attrs = getattr(tool, "attrs", {})
+ if "container_id" in attrs:
+ return attrs["container_id"]
+
+ # Check in nested configurations
+ config = kwargs.get("config", {})
+ if isinstance(config, dict) and "container_id" in config:
+ return config["container_id"]
+
+ logger.debug("Could not resolve container ID from agent context")
+ return None
+
+ def _generate_run_id(self, kwargs: Dict[str, Any]) -> str:
+ """
+ Generate or extract a run ID from the agent context.
+
+ Args:
+ kwargs: Agent run arguments
+
+ Returns:
+ Run ID string
+ """
+ # Look for existing run ID
+ if "run_id" in kwargs:
+ return kwargs["run_id"]
+
+ # Generate one based on timestamp and container
+ import time
+
+ timestamp = int(time.time())
+ container_id = self._current_container_id or "unknown"
+ return f"run_{container_id}_{timestamp}"
+
+ async def _create_snapshot_if_enabled(
+ self,
+ trigger: SnapshotTrigger,
+ description: Optional[str] = None,
+ action_context: Optional[str] = None,
+ ) -> None:
+ """
+ Create a snapshot if the trigger is enabled and container is available.
+
+ Args:
+ trigger: Snapshot trigger type
+ description: Optional description
+ action_context: Optional action context
+ """
+ if not self._current_container_id:
+ logger.debug(f"No container ID available for {trigger.value} snapshot")
+ return
+
+ if not await self.snapshot_manager.should_create_snapshot(trigger):
+ logger.debug(f"Snapshot trigger {trigger.value} is not enabled")
+ return
+
+ try:
+ await self.snapshot_manager.create_snapshot(
+ container_id=self._current_container_id,
+ trigger=trigger,
+ description=description,
+ action_context=action_context,
+ run_id=self._current_run_id,
+ )
+ logger.info(
+ f"Created {trigger.value} snapshot for container {self._current_container_id}"
+ )
+
+ except Exception as e:
+ logger.error(f"Failed to create {trigger.value} snapshot: {e}")
+ # Don't re-raise - snapshots are not critical to agent operation
+
+ # CUA Agent SDK Callback Methods
+ # These mirror the AsyncCallbackHandler interface from the CUA SDK
+
+ async def on_run_start(self, kwargs: Dict[str, Any], old_items: List[Dict[str, Any]]) -> None:
+ """Called at the start of an agent run loop."""
+ try:
+ # Resolve container information
+ self._current_container_id = self.container_resolver(kwargs)
+ self._current_run_id = self._generate_run_id(kwargs)
+ self._action_count = 0
+
+ logger.info(
+ f"Agent run started - container: {self._current_container_id}, run: {self._current_run_id}"
+ )
+
+ # Create run start snapshot if enabled
+ await self._create_snapshot_if_enabled(
+ SnapshotTrigger.RUN_START,
+ description="Snapshot at agent run start",
+ action_context="run_start",
+ )
+
+ except Exception as e:
+ logger.error(f"Error in on_run_start callback: {e}")
+
+ async def on_run_end(
+ self,
+ kwargs: Dict[str, Any],
+ old_items: List[Dict[str, Any]],
+ new_items: List[Dict[str, Any]],
+ ) -> None:
+ """Called at the end of an agent run loop."""
+ try:
+ # Create run end snapshot if enabled
+ await self._create_snapshot_if_enabled(
+ SnapshotTrigger.RUN_END,
+ description=f"Snapshot at agent run end (completed {self._action_count} actions)",
+ action_context="run_end",
+ )
+
+ logger.info(f"Agent run ended - run: {self._current_run_id}")
+
+ # Reset run state
+ self._current_run_id = None
+ self._current_container_id = None
+ self._action_count = 0
+
+ except Exception as e:
+ logger.error(f"Error in on_run_end callback: {e}")
+
+ async def on_computer_call_start(self, item: Dict[str, Any]) -> None:
+ """Called when a computer call is about to start."""
+ try:
+ self._action_count += 1
+ action_type = item.get("action", {}).get("type", "unknown")
+
+ logger.debug(f"Computer call starting: {action_type} (action #{self._action_count})")
+
+ # Create before-action snapshot if enabled
+ await self._create_snapshot_if_enabled(
+ SnapshotTrigger.BEFORE_ACTION,
+ description=f"Snapshot before action: {action_type}",
+ action_context=f"before_{action_type}",
+ )
+
+ except Exception as e:
+ logger.error(f"Error in on_computer_call_start callback: {e}")
+
+ async def on_computer_call_end(
+ self, item: Dict[str, Any], result: List[Dict[str, Any]]
+ ) -> None:
+ """Called when a computer call has completed."""
+ try:
+ action_type = item.get("action", {}).get("type", "unknown")
+
+ logger.debug(f"Computer call completed: {action_type}")
+
+ # Create after-action snapshot if enabled
+ await self._create_snapshot_if_enabled(
+ SnapshotTrigger.AFTER_ACTION,
+ description=f"Snapshot after action: {action_type}",
+ action_context=f"after_{action_type}",
+ )
+
+ except Exception as e:
+ logger.error(f"Error in on_computer_call_end callback: {e}")
+
+ async def on_function_call_start(self, item: Dict[str, Any]) -> None:
+ """Called when a function call is about to start."""
+ try:
+ function_name = item.get("function", {}).get("name", "unknown")
+
+ logger.debug(f"Function call starting: {function_name}")
+
+ # Count function calls as actions too
+ self._action_count += 1
+
+ # Create before-action snapshot if enabled
+ await self._create_snapshot_if_enabled(
+ SnapshotTrigger.BEFORE_ACTION,
+ description=f"Snapshot before function: {function_name}",
+ action_context=f"before_function_{function_name}",
+ )
+
+ except Exception as e:
+ logger.error(f"Error in on_function_call_start callback: {e}")
+
+ async def on_function_call_end(
+ self, item: Dict[str, Any], result: List[Dict[str, Any]]
+ ) -> None:
+ """Called when a function call has completed."""
+ try:
+ function_name = item.get("function", {}).get("name", "unknown")
+
+ logger.debug(f"Function call completed: {function_name}")
+
+ # Create after-action snapshot if enabled
+ await self._create_snapshot_if_enabled(
+ SnapshotTrigger.AFTER_ACTION,
+ description=f"Snapshot after function: {function_name}",
+ action_context=f"after_function_{function_name}",
+ )
+
+ except Exception as e:
+ logger.error(f"Error in on_function_call_end callback: {e}")
+
+ # Additional utility methods for manual snapshot control
+
+ async def create_manual_snapshot(self, description: str = "Manual snapshot") -> str:
+ """
+ Create a manual snapshot with the current agent context.
+
+ Args:
+ description: Description for the snapshot
+
+ Returns:
+ Snapshot ID
+
+ Raises:
+ Exception: If no container context is available or snapshot fails
+ """
+ if not self._current_container_id:
+ raise Exception("No active container context for manual snapshot")
+
+ metadata = await self.snapshot_manager.create_snapshot(
+ container_id=self._current_container_id,
+ trigger=SnapshotTrigger.MANUAL,
+ description=description,
+ action_context="manual",
+ run_id=self._current_run_id,
+ )
+
+ return metadata.snapshot_id
+
+ async def restore_latest_snapshot(self, container_name_suffix: str = "restored") -> str:
+ """
+ Restore the latest snapshot for the current container.
+
+ Args:
+ container_name_suffix: Suffix for the restored container name
+
+ Returns:
+ ID of the restored container
+
+ Raises:
+ Exception: If no snapshots are available or restore fails
+ """
+ if not self._current_container_id:
+ raise Exception("No active container context for snapshot restore")
+
+ snapshots = await self.snapshot_manager.list_snapshots(
+ container_id=self._current_container_id, limit=1
+ )
+
+ if not snapshots:
+ raise Exception(f"No snapshots available for container {self._current_container_id}")
+
+ latest_snapshot = snapshots[0]
+
+ from .models import RestoreOptions
+
+ options = RestoreOptions(
+ new_container_name=f"{latest_snapshot.container_name}_{container_name_suffix}"
+ )
+
+ return await self.snapshot_manager.restore_snapshot(latest_snapshot.snapshot_id, options)
+
+ def get_current_context(self) -> Dict[str, Any]:
+ """
+ Get the current agent execution context.
+
+ Returns:
+ Dictionary with current context information
+ """
+ return {
+ "container_id": self._current_container_id,
+ "run_id": self._current_run_id,
+ "action_count": self._action_count,
+ "snapshot_config": self.config.model_dump(),
+ }
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/cli.py b/libs/python/snapshot-manager/src/snapshot_manager/cli.py
new file mode 100644
index 00000000..24f9607b
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/cli.py
@@ -0,0 +1,313 @@
+"""
+Command-line interface for the snapshot manager.
+
+Provides a convenient CLI for testing and managing snapshots outside
+of the agent SDK integration.
+"""
+
+import asyncio
+import json
+import logging
+import sys
+
+import click
+
+from .manager import SnapshotManager
+from .models import RestoreOptions, SnapshotConfig, SnapshotTrigger
+
+# Configure logging
+logging.basicConfig(
+ level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+)
+logger = logging.getLogger(__name__)
+
+
+@click.group()
+@click.option("--config", type=click.Path(exists=True), help="Configuration file path")
+@click.option("--storage-path", default=".snapshots", help="Base path for snapshot storage")
+@click.option("--verbose", "-v", is_flag=True, help="Enable verbose logging")
+@click.pass_context
+def main(ctx, config, storage_path, verbose):
+ """CUA Snapshot Manager CLI - Manage container snapshots for the Cua Agent SDK."""
+
+ if verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ # Load configuration
+ if config:
+ try:
+ with open(config, "r") as f:
+ config_data = json.load(f)
+ # CLI storage_path takes precedence over config file
+ if storage_path != ".snapshots": # Only override if explicitly set
+ config_data["storage_path"] = storage_path
+ snapshot_config = SnapshotConfig(**config_data)
+ except Exception as e:
+ click.echo(f"Error loading config: {e}", err=True)
+ sys.exit(1)
+ else:
+ snapshot_config = SnapshotConfig(storage_path=storage_path)
+
+ # Initialize snapshot manager
+ from .providers import DockerSnapshotProvider
+ from .storage import FileSystemSnapshotStorage
+
+ storage = FileSystemSnapshotStorage(base_path=snapshot_config.storage_path)
+ provider = DockerSnapshotProvider()
+ manager = SnapshotManager(provider=provider, storage=storage, config=snapshot_config)
+
+ # Store in context for subcommands
+ ctx.ensure_object(dict)
+ ctx.obj["manager"] = manager
+ ctx.obj["config"] = snapshot_config
+
+
+@main.command()
+@click.argument("container_id")
+@click.option(
+ "--trigger",
+ type=click.Choice([t.value for t in SnapshotTrigger]),
+ default="manual",
+ help="Snapshot trigger type",
+)
+@click.option("--description", help="Description for the snapshot")
+@click.option("--context", help="Action context")
+@click.option("--run-id", help="Agent run ID")
+@click.pass_context
+def create(ctx, container_id, trigger, description, context, run_id):
+ """Create a snapshot of a container."""
+
+ async def _create():
+ manager = ctx.obj["manager"]
+
+ try:
+ metadata = await manager.create_snapshot(
+ container_id=container_id,
+ trigger=SnapshotTrigger(trigger),
+ description=description,
+ action_context=context,
+ run_id=run_id,
+ )
+
+ click.echo(f"✅ Created snapshot: {metadata.snapshot_id}")
+ click.echo(f" Container: {metadata.container_name}")
+ click.echo(f" Trigger: {metadata.trigger.value}")
+ click.echo(
+ f" Size: {metadata.size_bytes / (1024*1024):.1f} MB"
+ if metadata.size_bytes
+ else " Size: Unknown"
+ )
+ click.echo(f" Image: {metadata.image_tag}")
+
+ except Exception as e:
+ click.echo(f"❌ Error creating snapshot: {e}", err=True)
+ sys.exit(1)
+
+ asyncio.run(_create())
+
+
+@main.command()
+@click.option("--container", help="Filter by container ID")
+@click.option("--limit", type=int, help="Limit number of results")
+@click.option("--json-output", is_flag=True, help="Output as JSON")
+@click.pass_context
+def list(ctx, container, limit, json_output):
+ """List snapshots."""
+
+ async def _list():
+ manager = ctx.obj["manager"]
+
+ try:
+ snapshots = await manager.list_snapshots(container_id=container, limit=limit)
+
+ if json_output:
+ # Convert to JSON-serializable format
+ data = []
+ for snapshot in snapshots:
+ snapshot_dict = snapshot.model_dump()
+ snapshot_dict["timestamp"] = snapshot.timestamp.isoformat()
+ data.append(snapshot_dict)
+ click.echo(json.dumps(data, indent=2))
+ else:
+ if not snapshots:
+ click.echo("No snapshots found.")
+ return
+
+ click.echo(f"Found {len(snapshots)} snapshot(s):")
+ click.echo()
+
+ for snapshot in snapshots:
+ size_mb = snapshot.size_bytes / (1024 * 1024) if snapshot.size_bytes else 0
+ click.echo(f"📸 {snapshot.snapshot_id}")
+ click.echo(
+ f" Container: {snapshot.container_name} ({snapshot.container_id[:12]})"
+ )
+ click.echo(f" Created: {snapshot.timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
+ click.echo(f" Trigger: {snapshot.trigger.value}")
+ click.echo(f" Size: {size_mb:.1f} MB")
+ click.echo(f" Status: {snapshot.status.value}")
+ if snapshot.description:
+ click.echo(f" Description: {snapshot.description}")
+ click.echo()
+
+ except Exception as e:
+ click.echo(f"❌ Error listing snapshots: {e}", err=True)
+ sys.exit(1)
+
+ asyncio.run(_list())
+
+
+@main.command()
+@click.argument("snapshot_id")
+@click.option("--container-name", help="Name for the restored container")
+@click.option(
+ "--preserve-networks/--no-preserve-networks", default=True, help="Preserve network connections"
+)
+@click.option(
+ "--preserve-volumes/--no-preserve-volumes", default=True, help="Preserve volume mounts"
+)
+@click.pass_context
+def restore(ctx, snapshot_id, container_name, preserve_networks, preserve_volumes):
+ """Restore a container from a snapshot."""
+
+ async def _restore():
+ manager = ctx.obj["manager"]
+
+ try:
+ options = RestoreOptions(
+ new_container_name=container_name,
+ preserve_networks=preserve_networks,
+ preserve_volumes=preserve_volumes,
+ )
+
+ container_id = await manager.restore_snapshot(snapshot_id, options)
+
+ click.echo(f"✅ Restored container: {container_id}")
+
+ except Exception as e:
+ click.echo(f"❌ Error restoring snapshot: {e}", err=True)
+ sys.exit(1)
+
+ asyncio.run(_restore())
+
+
+@main.command()
+@click.argument("snapshot_id")
+@click.option("--force", is_flag=True, help="Force deletion without confirmation")
+@click.pass_context
+def delete(ctx, snapshot_id, force):
+ """Delete a snapshot."""
+
+ async def _delete():
+ manager = ctx.obj["manager"]
+
+ try:
+ if not force:
+ # Get snapshot info for confirmation
+ metadata = await manager.get_snapshot(snapshot_id)
+ if metadata:
+ click.echo("About to delete snapshot:")
+ click.echo(f" ID: {metadata.snapshot_id}")
+ click.echo(f" Container: {metadata.container_name}")
+ click.echo(f" Created: {metadata.timestamp}")
+ click.echo(
+ f" Size: {metadata.size_bytes / (1024*1024):.1f} MB"
+ if metadata.size_bytes
+ else " Size: Unknown"
+ )
+
+ if not click.confirm("Are you sure you want to delete this snapshot?"):
+ click.echo("Deletion cancelled.")
+ return
+
+ await manager.delete_snapshot(snapshot_id)
+ click.echo(f"✅ Deleted snapshot: {snapshot_id}")
+
+ except Exception as e:
+ click.echo(f"❌ Error deleting snapshot: {e}", err=True)
+ sys.exit(1)
+
+ asyncio.run(_delete())
+
+
+@main.command()
+@click.option("--max-age-days", type=int, help="Maximum age in days for snapshots to keep")
+@click.option(
+ "--dry-run", is_flag=True, help="Show what would be deleted without actually deleting"
+)
+@click.pass_context
+def cleanup(ctx, max_age_days, dry_run):
+ """Clean up old snapshots."""
+
+ async def _cleanup():
+ manager = ctx.obj["manager"]
+
+ try:
+ if dry_run:
+ click.echo("🔍 Dry run mode not implemented yet")
+ return
+ else:
+ count = await manager.cleanup_old_snapshots(max_age_days)
+ click.echo(f"✅ Cleaned up {count} old snapshots")
+
+ except Exception as e:
+ click.echo(f"❌ Error during cleanup: {e}", err=True)
+ sys.exit(1)
+
+ asyncio.run(_cleanup())
+
+
+@main.command()
+@click.pass_context
+def stats(ctx):
+ """Show storage statistics."""
+
+ async def _stats():
+ manager = ctx.obj["manager"]
+
+ try:
+ stats = await manager.get_storage_stats()
+
+ click.echo("📊 Snapshot Storage Statistics:")
+ click.echo(f" Total snapshots: {stats['total_snapshots']}")
+ click.echo(f" Total containers: {stats['total_containers']}")
+ click.echo(f" Total size: {stats['total_size_gb']:.2f} GB")
+ click.echo(f" Metadata size: {stats['metadata_size_bytes'] / 1024:.1f} KB")
+ click.echo(f" Storage path: {stats['storage_path']}")
+ if stats.get("last_updated"):
+ click.echo(f" Last updated: {stats['last_updated']}")
+
+ except Exception as e:
+ click.echo(f"❌ Error getting stats: {e}", err=True)
+ sys.exit(1)
+
+ asyncio.run(_stats())
+
+
+@main.command()
+@click.argument("container_id")
+@click.pass_context
+def validate(ctx, container_id):
+ """Validate that a container can be snapshotted."""
+
+ async def _validate():
+ manager = ctx.obj["manager"]
+
+ try:
+ is_valid = await manager.provider.validate_container(container_id)
+
+ if is_valid:
+ click.echo(f"✅ Container {container_id} is valid for snapshotting")
+ else:
+ click.echo(f"❌ Container {container_id} is not valid for snapshotting")
+ sys.exit(1)
+
+ except Exception as e:
+ click.echo(f"❌ Error validating container: {e}", err=True)
+ sys.exit(1)
+
+ asyncio.run(_validate())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/interfaces.py b/libs/python/snapshot-manager/src/snapshot_manager/interfaces.py
new file mode 100644
index 00000000..16d9254b
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/interfaces.py
@@ -0,0 +1,208 @@
+"""
+Abstract interfaces for the snapshot management system.
+
+These interfaces define the contracts that different components
+must implement to be pluggable within the system.
+"""
+
+from abc import ABC, abstractmethod
+from typing import Any, Dict, List, Optional
+
+from .models import RestoreOptions, SnapshotMetadata
+
+
+class SnapshotProvider(ABC):
+ """
+ Abstract interface for snapshot providers.
+
+ A snapshot provider handles the actual creation and restoration
+ of snapshots for a specific container technology (Docker, etc.).
+ """
+
+ @abstractmethod
+ async def create_snapshot(
+ self, container_id: str, metadata: SnapshotMetadata
+ ) -> SnapshotMetadata:
+ """
+ Create a snapshot of the specified container.
+
+ Args:
+ container_id: ID of the container to snapshot
+ metadata: Metadata for the snapshot (will be updated with storage info)
+
+ Returns:
+ Updated metadata with storage information filled in
+
+ Raises:
+ SnapshotError: If snapshot creation fails
+ """
+ pass
+
+ @abstractmethod
+ async def restore_snapshot(
+ self, metadata: SnapshotMetadata, options: Optional[RestoreOptions] = None
+ ) -> str:
+ """
+ Restore a container from a snapshot.
+
+ Args:
+ metadata: Metadata of the snapshot to restore
+ options: Optional restore configuration
+
+ Returns:
+ ID of the restored container
+
+ Raises:
+ SnapshotError: If restore fails
+ """
+ pass
+
+ @abstractmethod
+ async def delete_snapshot(self, metadata: SnapshotMetadata) -> None:
+ """
+ Delete a snapshot and clean up its storage.
+
+ Args:
+ metadata: Metadata of the snapshot to delete
+
+ Raises:
+ SnapshotError: If deletion fails
+ """
+ pass
+
+ @abstractmethod
+ async def get_snapshot_size(self, metadata: SnapshotMetadata) -> int:
+ """
+ Get the size of a snapshot in bytes.
+
+ Args:
+ metadata: Metadata of the snapshot
+
+ Returns:
+ Size in bytes
+ """
+ pass
+
+ @abstractmethod
+ async def validate_container(self, container_id: str) -> bool:
+ """
+ Validate that a container exists and can be snapshotted.
+
+ Args:
+ container_id: ID of the container to validate
+
+ Returns:
+ True if container is valid for snapshotting
+ """
+ pass
+
+
+class SnapshotStorage(ABC):
+ """
+ Abstract interface for snapshot metadata storage.
+
+ Handles persistence of snapshot metadata and indexing
+ for efficient retrieval and management.
+ """
+
+ @abstractmethod
+ async def save_metadata(self, metadata: SnapshotMetadata) -> None:
+ """
+ Save snapshot metadata to persistent storage.
+
+ Args:
+ metadata: Metadata to save
+
+ Raises:
+ StorageError: If save operation fails
+ """
+ pass
+
+ @abstractmethod
+ async def load_metadata(self, snapshot_id: str) -> Optional[SnapshotMetadata]:
+ """
+ Load snapshot metadata by ID.
+
+ Args:
+ snapshot_id: ID of the snapshot
+
+ Returns:
+ Metadata if found, None otherwise
+ """
+ pass
+
+ @abstractmethod
+ async def list_snapshots(
+ self, container_id: Optional[str] = None, limit: Optional[int] = None
+ ) -> List[SnapshotMetadata]:
+ """
+ List snapshots, optionally filtered by container.
+
+ Args:
+ container_id: Optional filter by container ID
+ limit: Optional limit on number of results
+
+ Returns:
+ List of snapshot metadata
+ """
+ pass
+
+ @abstractmethod
+ async def delete_metadata(self, snapshot_id: str) -> None:
+ """
+ Delete snapshot metadata from storage.
+
+ Args:
+ snapshot_id: ID of the snapshot to delete
+
+ Raises:
+ StorageError: If deletion fails
+ """
+ pass
+
+ @abstractmethod
+ async def update_metadata(self, metadata: SnapshotMetadata) -> None:
+ """
+ Update existing snapshot metadata.
+
+ Args:
+ metadata: Updated metadata
+
+ Raises:
+ StorageError: If update fails
+ """
+ pass
+
+ @abstractmethod
+ async def get_storage_stats(self) -> Dict[str, Any]:
+ """
+ Get storage statistics (total size, count, etc.).
+
+ Returns:
+ Dictionary with storage statistics
+ """
+ pass
+
+
+class SnapshotError(Exception):
+ """Base exception for snapshot operations."""
+
+ pass
+
+
+class StorageError(Exception):
+ """Exception for storage operations."""
+
+ pass
+
+
+class ContainerNotFoundError(SnapshotError):
+ """Exception raised when a container is not found."""
+
+ pass
+
+
+class SnapshotNotFoundError(SnapshotError):
+ """Exception raised when a snapshot is not found."""
+
+ pass
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/manager.py b/libs/python/snapshot-manager/src/snapshot_manager/manager.py
new file mode 100644
index 00000000..4835c76d
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/manager.py
@@ -0,0 +1,412 @@
+"""
+Main snapshot manager that orchestrates snapshot operations.
+
+This is the primary interface for the snapshot system, coordinating
+between providers, storage, and cleanup policies.
+"""
+
+import asyncio
+import logging
+import uuid
+from datetime import datetime, timedelta
+from typing import Any, Dict, List, Optional
+
+from .interfaces import SnapshotError, SnapshotProvider, SnapshotStorage
+from .models import (
+ RestoreOptions,
+ SnapshotConfig,
+ SnapshotMetadata,
+ SnapshotStatus,
+ SnapshotTrigger,
+)
+from .providers import DockerSnapshotProvider
+from .storage import FileSystemSnapshotStorage
+
+logger = logging.getLogger(__name__)
+
+
+class SnapshotManager:
+ """
+ Main coordinator for snapshot operations.
+
+ Manages the creation, storage, restoration, and cleanup of container
+ snapshots according to configured policies and triggers.
+ """
+
+ def __init__(
+ self,
+ provider: Optional[SnapshotProvider] = None,
+ storage: Optional[SnapshotStorage] = None,
+ config: Optional[SnapshotConfig] = None,
+ ):
+ """
+ Initialize the snapshot manager.
+
+ Args:
+ provider: Snapshot provider (defaults to DockerSnapshotProvider)
+ storage: Storage backend (defaults to FileSystemSnapshotStorage)
+ config: Configuration (defaults to SnapshotConfig with sensible defaults)
+ """
+ self.config = config or SnapshotConfig()
+ self.provider = provider or DockerSnapshotProvider()
+ self.storage = storage or FileSystemSnapshotStorage(base_path=self.config.storage_path)
+
+ # Track active operations to prevent conflicts
+ self._active_operations: Dict[str, str] = {} # container_id -> operation_type
+ self._operation_lock = asyncio.Lock()
+
+ logger.info(f"SnapshotManager initialized with {type(self.provider).__name__} provider")
+
+ def _generate_snapshot_id(self) -> str:
+ """Generate a unique snapshot ID."""
+ return str(uuid.uuid4())
+
+ def _format_snapshot_name(self, metadata: SnapshotMetadata) -> str:
+ """
+ Format a human-readable snapshot name based on the configured pattern.
+
+ Args:
+ metadata: Snapshot metadata
+
+ Returns:
+ Formatted snapshot name
+ """
+ timestamp = metadata.timestamp.strftime("%Y%m%d_%H%M%S")
+
+ format_vars = {
+ "container_name": metadata.container_name,
+ "trigger": metadata.trigger.value,
+ "timestamp": timestamp,
+ "snapshot_id": metadata.snapshot_id[:8], # Short version
+ }
+
+ try:
+ return self.config.naming_pattern.format(**format_vars)
+ except KeyError as e:
+ logger.warning(f"Invalid naming pattern variable {e}, using default")
+ return f"{metadata.container_name}_{metadata.trigger.value}_{timestamp}"
+
+ async def _check_operation_conflict(self, container_id: str, operation: str) -> None:
+ """
+ Check if there's a conflicting operation in progress for this container.
+
+ Args:
+ container_id: ID of the container
+ operation: Type of operation being attempted
+
+ Raises:
+ SnapshotError: If there's a conflicting operation
+ """
+ async with self._operation_lock:
+ if container_id in self._active_operations:
+ active_op = self._active_operations[container_id]
+ raise SnapshotError(f"Container {container_id} is already {active_op}")
+
+ self._active_operations[container_id] = operation
+
+ async def _clear_operation(self, container_id: str) -> None:
+ """Clear the active operation for a container."""
+ async with self._operation_lock:
+ self._active_operations.pop(container_id, None)
+
+ async def create_snapshot(
+ self,
+ container_id: str,
+ trigger: SnapshotTrigger = SnapshotTrigger.MANUAL,
+ description: Optional[str] = None,
+ action_context: Optional[str] = None,
+ run_id: Optional[str] = None,
+ labels: Optional[Dict[str, str]] = None,
+ ) -> SnapshotMetadata:
+ """
+ Create a new snapshot of the specified container.
+
+ Args:
+ container_id: ID or name of the container to snapshot
+ trigger: What triggered this snapshot
+ description: Optional human-readable description
+ action_context: Context of the action that triggered the snapshot
+ run_id: ID of the agent run this snapshot belongs to
+ labels: Additional labels/tags for the snapshot
+
+ Returns:
+ Metadata of the created snapshot
+
+ Raises:
+ SnapshotError: If snapshot creation fails
+ """
+ await self._check_operation_conflict(container_id, "creating snapshot")
+
+ try:
+ logger.info(f"Creating snapshot for container {container_id}, trigger: {trigger.value}")
+
+ # Validate container exists
+ if not await self.provider.validate_container(container_id):
+ raise SnapshotError(f"Container {container_id} is not valid for snapshotting")
+
+ # Create metadata
+ snapshot_id = self._generate_snapshot_id()
+ metadata = SnapshotMetadata(
+ snapshot_id=snapshot_id,
+ container_id=container_id,
+ container_name=container_id, # Will be updated by provider
+ trigger=trigger,
+ description=description,
+ action_context=action_context,
+ run_id=run_id,
+ labels=labels or {},
+ image_id=None,
+ image_tag=None,
+ storage_path=None,
+ size_bytes=None,
+ parent_snapshot_id=None,
+ )
+
+ # Check if we need to enforce limits before creating
+ await self._enforce_snapshot_limits(container_id)
+
+ # Create the snapshot using the provider
+ metadata = await self.provider.create_snapshot(container_id, metadata)
+
+ # Save volume backups if any were created
+ if hasattr(metadata, '_volume_backups') and metadata._volume_backups:
+ logger.info(f"Storing {len(metadata._volume_backups)} volume backups...")
+
+ # Check if storage supports volume operations
+ if hasattr(self.storage, 'save_volume_data'):
+ for volume_name, tar_data in metadata._volume_backups.items():
+ await self.storage.save_volume_data(metadata.snapshot_id, volume_name, tar_data)
+ logger.debug(f"Stored volume backup: {volume_name} ({len(tar_data)} bytes)")
+ else:
+ logger.warning("Storage backend does not support volume data - volumes not stored")
+
+ # Clean up temporary attribute
+ delattr(metadata, '_volume_backups')
+
+ # Save metadata to storage
+ await self.storage.save_metadata(metadata)
+
+ logger.info(f"Successfully created snapshot {snapshot_id} for container {container_id}")
+ return metadata
+
+ except Exception as e:
+ logger.error(f"Failed to create snapshot for container {container_id}: {e}")
+ raise
+ finally:
+ await self._clear_operation(container_id)
+
+ async def restore_snapshot(
+ self, snapshot_id: str, options: Optional[RestoreOptions] = None
+ ) -> str:
+ """
+ Restore a container from a snapshot.
+
+ Args:
+ snapshot_id: ID of the snapshot to restore
+ options: Optional restore configuration
+
+ Returns:
+ ID of the restored container
+
+ Raises:
+ SnapshotError: If restore fails
+ """
+ logger.info(f"Restoring snapshot {snapshot_id}")
+
+ # Load metadata
+ metadata = await self.storage.load_metadata(snapshot_id)
+ if not metadata:
+ raise SnapshotError(f"Snapshot {snapshot_id} not found")
+
+ if metadata.status != SnapshotStatus.COMPLETED:
+ raise SnapshotError(
+ f"Snapshot {snapshot_id} is not in completed state (status: {metadata.status})"
+ )
+
+ try:
+ # Check for operation conflicts on the original container
+ # (in case someone tries to restore while creating a snapshot)
+ await self._check_operation_conflict(metadata.container_id, "restoring snapshot")
+
+ # Restore using the provider
+ container_id = await self.provider.restore_snapshot(metadata, options)
+
+ # Update metadata
+ metadata.restoration_count += 1
+ await self.storage.update_metadata(metadata)
+
+ logger.info(
+ f"Successfully restored container {container_id} from snapshot {snapshot_id}"
+ )
+ return container_id
+
+ except Exception as e:
+ logger.error(f"Failed to restore snapshot {snapshot_id}: {e}")
+ raise
+ finally:
+ await self._clear_operation(metadata.container_id)
+
+ async def delete_snapshot(self, snapshot_id: str) -> None:
+ """
+ Delete a snapshot and clean up its storage.
+
+ Args:
+ snapshot_id: ID of the snapshot to delete
+
+ Raises:
+ SnapshotError: If deletion fails
+ """
+ logger.info(f"Deleting snapshot {snapshot_id}")
+
+ # Load metadata
+ metadata = await self.storage.load_metadata(snapshot_id)
+ if not metadata:
+ logger.warning(
+ f"Snapshot {snapshot_id} metadata not found, but proceeding with cleanup"
+ )
+ return
+
+ try:
+ # Delete using the provider
+ await self.provider.delete_snapshot(metadata)
+
+ # Remove metadata from storage
+ await self.storage.delete_metadata(snapshot_id)
+
+ logger.info(f"Successfully deleted snapshot {snapshot_id}")
+
+ except Exception as e:
+ logger.error(f"Failed to delete snapshot {snapshot_id}: {e}")
+ raise
+
+ async def list_snapshots(
+ self, container_id: Optional[str] = None, limit: Optional[int] = None
+ ) -> List[SnapshotMetadata]:
+ """
+ List snapshots, optionally filtered by container.
+
+ Args:
+ container_id: Optional filter by container ID
+ limit: Optional limit on number of results
+
+ Returns:
+ List of snapshot metadata
+ """
+ return await self.storage.list_snapshots(container_id, limit)
+
+ async def get_snapshot(self, snapshot_id: str) -> Optional[SnapshotMetadata]:
+ """
+ Get metadata for a specific snapshot.
+
+ Args:
+ snapshot_id: ID of the snapshot
+
+ Returns:
+ Snapshot metadata if found, None otherwise
+ """
+ return await self.storage.load_metadata(snapshot_id)
+
+ async def _enforce_snapshot_limits(self, container_id: str) -> None:
+ """
+ Enforce snapshot limits by cleaning up old snapshots if necessary.
+
+ Args:
+ container_id: ID of the container
+ """
+ # Check per-container limit
+ container_snapshots = await self.storage.list_snapshots(container_id)
+ if len(container_snapshots) >= self.config.max_snapshots_per_container:
+ # Delete oldest snapshots to make room
+ snapshots_to_delete = container_snapshots[self.config.max_snapshots_per_container - 1 :]
+ for snapshot in snapshots_to_delete:
+ try:
+ await self.delete_snapshot(snapshot.snapshot_id)
+ logger.info(
+ f"Auto-deleted old snapshot {snapshot.snapshot_id} due to per-container limit"
+ )
+ except Exception as e:
+ logger.error(f"Failed to auto-delete snapshot {snapshot.snapshot_id}: {e}")
+
+ # Check total snapshot limit
+ all_snapshots = await self.storage.list_snapshots()
+ if len(all_snapshots) >= self.config.max_total_snapshots:
+ # Delete oldest snapshots globally
+ snapshots_to_delete = all_snapshots[self.config.max_total_snapshots - 1 :]
+ for snapshot in snapshots_to_delete:
+ try:
+ await self.delete_snapshot(snapshot.snapshot_id)
+ logger.info(
+ f"Auto-deleted old snapshot {snapshot.snapshot_id} due to total limit"
+ )
+ except Exception as e:
+ logger.error(f"Failed to auto-delete snapshot {snapshot.snapshot_id}: {e}")
+
+ # Check storage size limit
+ stats = await self.storage.get_storage_stats()
+ if stats["total_size_gb"] > self.config.max_storage_size_gb:
+ # Delete oldest snapshots until under limit
+ all_snapshots = await self.storage.list_snapshots()
+ for snapshot in reversed(all_snapshots): # Oldest first
+ try:
+ await self.delete_snapshot(snapshot.snapshot_id)
+ logger.info(
+ f"Auto-deleted snapshot {snapshot.snapshot_id} due to storage size limit"
+ )
+
+ # Check if we're now under the limit
+ stats = await self.storage.get_storage_stats()
+ if stats["total_size_gb"] <= self.config.max_storage_size_gb:
+ break
+ except Exception as e:
+ logger.error(f"Failed to auto-delete snapshot {snapshot.snapshot_id}: {e}")
+
+ async def cleanup_old_snapshots(self, max_age_days: Optional[int] = None) -> int:
+ """
+ Clean up snapshots older than the specified age.
+
+ Args:
+ max_age_days: Maximum age in days (defaults to config.auto_cleanup_days)
+
+ Returns:
+ Number of snapshots cleaned up
+ """
+ max_age = max_age_days or self.config.auto_cleanup_days
+ cutoff_date = datetime.now() - timedelta(days=max_age)
+
+ logger.info(f"Cleaning up snapshots older than {max_age} days (before {cutoff_date})")
+
+ all_snapshots = await self.storage.list_snapshots()
+ cleanup_count = 0
+
+ for snapshot in all_snapshots:
+ if snapshot.timestamp < cutoff_date:
+ try:
+ await self.delete_snapshot(snapshot.snapshot_id)
+ cleanup_count += 1
+ logger.info(f"Cleaned up old snapshot {snapshot.snapshot_id}")
+ except Exception as e:
+ logger.error(f"Failed to clean up snapshot {snapshot.snapshot_id}: {e}")
+
+ logger.info(f"Cleaned up {cleanup_count} old snapshots")
+ return cleanup_count
+
+ async def get_storage_stats(self) -> Dict[str, Any]:
+ """
+ Get storage statistics and system information.
+
+ Returns:
+ Dictionary with storage statistics
+ """
+ return await self.storage.get_storage_stats()
+
+ async def should_create_snapshot(self, trigger: SnapshotTrigger) -> bool:
+ """
+ Check if a snapshot should be created for the given trigger.
+
+ Args:
+ trigger: The trigger type
+
+ Returns:
+ True if snapshot should be created
+ """
+ return trigger in self.config.triggers
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/models.py b/libs/python/snapshot-manager/src/snapshot_manager/models.py
new file mode 100644
index 00000000..e3783a8a
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/models.py
@@ -0,0 +1,157 @@
+"""
+Data models for snapshot management system.
+"""
+
+from datetime import datetime
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel, Field
+
+
+class SnapshotTrigger(str, Enum):
+ """Enumeration of snapshot trigger types."""
+
+ MANUAL = "manual"
+ RUN_START = "run_start"
+ RUN_END = "run_end"
+ AFTER_ACTION = "after_action"
+ BEFORE_ACTION = "before_action"
+ ON_ERROR = "on_error"
+ PERIODIC = "periodic"
+
+
+class SnapshotStatus(str, Enum):
+ """Enumeration of snapshot status types."""
+
+ CREATING = "creating"
+ COMPLETED = "completed"
+ FAILED = "failed"
+ RESTORING = "restoring"
+ DELETED = "deleted"
+
+
+class SnapshotMetadata(BaseModel):
+ """
+ Metadata associated with a container snapshot.
+
+ This contains all the information needed to identify, manage,
+ and restore snapshots effectively.
+ """
+
+ # Core identification
+ snapshot_id: str = Field(..., description="Unique identifier for the snapshot")
+ container_id: str = Field(..., description="ID of the container that was snapshotted")
+ container_name: str = Field(..., description="Name of the container")
+
+ # Temporal information
+ timestamp: datetime = Field(
+ default_factory=datetime.now, description="When the snapshot was created"
+ )
+
+ # Context information
+ trigger: SnapshotTrigger = Field(..., description="What triggered this snapshot")
+ action_context: Optional[str] = Field(
+ None, description="Context of the action that triggered the snapshot"
+ )
+ run_id: Optional[str] = Field(None, description="ID of the agent run this snapshot belongs to")
+
+ # Status and lifecycle
+ status: SnapshotStatus = Field(
+ default=SnapshotStatus.CREATING, description="Current status of the snapshot"
+ )
+
+ # Storage information
+ image_id: Optional[str] = Field(None, description="Docker image ID created from the snapshot")
+ image_tag: Optional[str] = Field(None, description="Docker image tag")
+ storage_path: Optional[str] = Field(None, description="Path where snapshot data is stored")
+ size_bytes: Optional[int] = Field(None, description="Size of the snapshot in bytes")
+
+ # Additional metadata
+ description: Optional[str] = Field(None, description="Human-readable description")
+ labels: Dict[str, str] = Field(default_factory=dict, description="Additional labels/tags")
+ agent_metadata: Dict[str, Any] = Field(
+ default_factory=dict, description="Agent-specific metadata"
+ )
+
+ # Restoration information
+ parent_snapshot_id: Optional[str] = Field(
+ None, description="ID of parent snapshot if this is a restored state"
+ )
+ restoration_count: int = Field(
+ default=0, description="Number of times this snapshot has been restored"
+ )
+
+ class Config:
+ """Pydantic configuration."""
+
+ json_encoders = {datetime: lambda v: v.isoformat()}
+
+
+class SnapshotConfig(BaseModel):
+ """
+ Configuration for snapshot management behavior.
+
+ This controls when snapshots are taken, how they're stored,
+ and cleanup policies.
+ """
+
+ # Trigger configuration
+ triggers: List[SnapshotTrigger] = Field(
+ default=[SnapshotTrigger.RUN_START, SnapshotTrigger.RUN_END],
+ description="List of triggers that should create snapshots",
+ )
+
+ # Storage configuration
+ storage_path: str = Field(default=".snapshots", description="Base path for snapshot storage")
+ max_snapshots_per_container: int = Field(
+ default=10, description="Maximum snapshots to keep per container"
+ )
+ max_total_snapshots: int = Field(
+ default=100, description="Maximum total snapshots across all containers"
+ )
+ max_storage_size_gb: float = Field(default=10.0, description="Maximum storage size in GB")
+
+ # Cleanup configuration
+ cleanup_on_exit: bool = Field(
+ default=True, description="Whether to clean up old snapshots on exit"
+ )
+ auto_cleanup_days: int = Field(
+ default=7, description="Auto-delete snapshots older than this many days"
+ )
+
+ # Performance configuration
+ compression_enabled: bool = Field(default=True, description="Whether to compress snapshots")
+ parallel_operations: bool = Field(
+ default=True, description="Whether to allow parallel snapshot operations"
+ )
+
+ # Integration configuration
+ include_volumes: bool = Field(
+ default=True, description="Whether to include mounted volumes in snapshots"
+ )
+ exclude_paths: List[str] = Field(
+ default_factory=lambda: ["/tmp", "/var/tmp", "/proc", "/sys"],
+ description="Paths to exclude from snapshots",
+ )
+
+ # Naming configuration
+ naming_pattern: str = Field(
+ default="{container_name}_{trigger}_{timestamp}", description="Pattern for snapshot naming"
+ )
+
+
+class RestoreOptions(BaseModel):
+ """Options for restoring from a snapshot."""
+
+ new_container_name: Optional[str] = Field(None, description="Name for the restored container")
+ preserve_networks: bool = Field(
+ default=True, description="Whether to preserve network connections"
+ )
+ preserve_volumes: bool = Field(default=True, description="Whether to preserve volume mounts")
+ environment_overrides: Dict[str, str] = Field(
+ default_factory=dict, description="Environment variables to override"
+ )
+ port_mappings: Dict[str, str] = Field(
+ default_factory=dict, description="Port mappings to override (container_port: host_port)"
+ )
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/providers/__init__.py b/libs/python/snapshot-manager/src/snapshot_manager/providers/__init__.py
new file mode 100644
index 00000000..80ad020a
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/providers/__init__.py
@@ -0,0 +1,7 @@
+"""
+Snapshot providers for different container technologies.
+"""
+
+from .docker_provider import DockerSnapshotProvider
+
+__all__ = ["DockerSnapshotProvider"]
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/providers/docker_provider.py b/libs/python/snapshot-manager/src/snapshot_manager/providers/docker_provider.py
new file mode 100644
index 00000000..89f6a474
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/providers/docker_provider.py
@@ -0,0 +1,747 @@
+"""
+Docker implementation of the snapshot provider.
+
+This provider uses Docker's native commit and save/load functionality
+to create efficient container snapshots.
+"""
+
+import logging
+from datetime import datetime
+from io import BytesIO
+from typing import Any, Dict, Optional
+
+import docker
+from docker.errors import DockerException, NotFound
+
+from ..interfaces import ContainerNotFoundError, SnapshotError, SnapshotProvider
+from ..models import RestoreOptions, SnapshotMetadata, SnapshotStatus
+
+logger = logging.getLogger(__name__)
+
+
+class DockerSnapshotProvider(SnapshotProvider):
+ """
+ Docker-based snapshot provider.
+
+ Uses Docker's commit functionality to create container snapshots
+ as new images, providing efficient storage and fast restoration.
+ """
+
+ def __init__(self, docker_client: Optional[docker.DockerClient] = None):
+ """
+ Initialize the Docker snapshot provider.
+
+ Args:
+ docker_client: Optional Docker client instance. If None, creates a new one.
+ """
+ self.client = docker_client or docker.from_env()
+ self._validate_docker_connection()
+
+ def _validate_docker_connection(self) -> None:
+ """Validate that Docker is available and accessible."""
+ try:
+ self.client.ping()
+ logger.info("Docker connection validated successfully")
+ except DockerException as e:
+ raise SnapshotError(f"Failed to connect to Docker: {e}")
+
+ def _analyze_container_volumes(self, container_id: str) -> Dict[str, Any]:
+ """
+ Analyze volumes attached to a container.
+
+ Args:
+ container_id: ID of the container to analyze
+
+ Returns:
+ Dictionary with volume analysis results
+ """
+ try:
+ container = self.client.containers.get(container_id)
+ mounts = container.attrs.get("Mounts", [])
+
+ logger.debug(f"Container mounts structure: {mounts}")
+
+ named_volumes = []
+ bind_mounts = []
+
+ for mount in mounts:
+ # Handle different mount structure formats
+ if isinstance(mount, dict):
+ mount_info = {
+ "type": mount.get("Type"),
+ "source": mount.get("Source"),
+ "destination": mount.get("Destination"),
+ "mode": mount.get("Mode", "rw"),
+ "rw": mount.get("RW", True),
+ "name": mount.get("Name"),
+ }
+
+ if mount.get("Type") == "volume":
+ named_volumes.append(mount_info)
+ elif mount.get("Type") == "bind":
+ bind_mounts.append(mount_info)
+ else:
+ logger.warning(f"Unexpected mount structure: {type(mount)} - {mount}")
+
+ return {
+ "named_volumes": named_volumes,
+ "bind_mounts": bind_mounts,
+ "total_volumes": len(named_volumes),
+ "total_bind_mounts": len(bind_mounts),
+ }
+
+ except NotFound:
+ raise ContainerNotFoundError(f"Container {container_id} not found")
+ except DockerException as e:
+ raise SnapshotError(f"Failed to analyze container volumes: {e}")
+
+ def _backup_named_volume_as_tar(self, volume_name: str) -> bytes:
+ """
+ Create a tar archive of a named volume's data.
+
+ Args:
+ volume_name: Name of the Docker volume to backup
+
+ Returns:
+ Tar archive as bytes
+
+ Raises:
+ SnapshotError: If volume backup fails
+ """
+ try:
+ logger.info(f"Creating tar backup of volume: {volume_name}")
+
+ # Verify volume exists
+ try:
+ volume = self.client.volumes.get(volume_name)
+ logger.debug(f"Volume {volume_name} found: {volume.attrs}")
+ except NotFound:
+ raise SnapshotError(f"Volume {volume_name} not found")
+
+ # Create temporary container to access volume data
+ # Using alpine for minimal footprint
+ temp_container_config = {
+ "image": "alpine:latest",
+ "command": "sleep 30",
+ "volumes": {volume_name: {"bind": "/volume_data", "mode": "ro"}},
+ "detach": True,
+ "remove": False,
+ "name": f"volume-backup-{volume_name}-temp",
+ }
+
+ temp_container = None
+ try:
+ # Pull alpine if not available (but don't wait too long)
+ try:
+ self.client.images.get("alpine:latest")
+ except NotFound:
+ logger.info("Pulling alpine:latest for volume backup...")
+ self.client.images.pull("alpine:latest")
+
+ # Create and start temporary container
+ temp_container = self.client.containers.run(**temp_container_config)
+ logger.debug(f"Created temporary container: {temp_container.name}")
+
+ # Create tar archive of volume contents
+ tar_stream, _ = temp_container.get_archive("/volume_data")
+
+ # Convert stream to bytes
+ tar_data = BytesIO()
+ for chunk in tar_stream:
+ tar_data.write(chunk)
+
+ tar_bytes = tar_data.getvalue()
+ logger.info(f"Volume {volume_name} backed up: {len(tar_bytes)} bytes")
+
+ return tar_bytes
+
+ finally:
+ # Clean up temporary container
+ if temp_container:
+ try:
+ temp_container.stop(timeout=5)
+ temp_container.remove()
+ logger.debug(f"Cleaned up temporary container: {temp_container.name}")
+ except Exception as e:
+ logger.warning(f"Failed to clean up temporary container: {e}")
+
+ except DockerException as e:
+ raise SnapshotError(f"Failed to backup volume {volume_name}: {e}")
+ except Exception as e:
+ raise SnapshotError(f"Unexpected error backing up volume {volume_name}: {e}")
+
+ def _backup_container_volumes(self, volume_analysis: Dict[str, Any]) -> Dict[str, bytes]:
+ """
+ Backup all named volumes from a container's volume analysis.
+
+ Args:
+ volume_analysis: Result from _analyze_container_volumes
+
+ Returns:
+ Dictionary mapping volume names to tar archive bytes
+
+ Raises:
+ SnapshotError: If any volume backup fails
+ """
+ volume_backups = {}
+
+ # Process named volumes
+ for volume_info in volume_analysis.get("named_volumes", []):
+ volume_name = volume_info.get("name")
+ if not volume_name:
+ logger.warning(f"Skipping volume without name: {volume_info}")
+ continue
+
+ try:
+ tar_data = self._backup_named_volume_as_tar(volume_name)
+ volume_backups[volume_name] = tar_data
+ logger.info(f"Successfully backed up volume: {volume_name}")
+ except Exception as e:
+ logger.error(f"Failed to backup volume {volume_name}: {e}")
+ # Continue with other volumes, but track the failure
+ # We could make this configurable (fail-fast vs best-effort)
+ raise SnapshotError(f"Volume backup failed for {volume_name}: {e}")
+
+ # Log bind mount warnings
+ bind_mounts = volume_analysis.get("bind_mounts", [])
+ if bind_mounts:
+ logger.warning(
+ f"⚠️ {len(bind_mounts)} bind mount(s) detected - NOT included in snapshot:"
+ )
+ for mount in bind_mounts:
+ logger.warning(f" Bind mount: {mount.get('source')} → {mount.get('destination')}")
+ logger.warning(" Recommendation: Use named volumes for portable snapshots")
+
+ logger.info(
+ f"Volume backup completed: {len(volume_backups)} volumes, {len(bind_mounts)} bind mounts skipped"
+ )
+ return volume_backups
+
+ def _restore_volume_from_tar(self, volume_name: str, tar_data: bytes) -> None:
+ """
+ Restore a volume from tar archive data.
+
+ Args:
+ volume_name: Name of the volume to create/restore
+ tar_data: Tar archive data to extract
+
+ Raises:
+ SnapshotError: If volume restore fails
+ """
+ try:
+ logger.info(f"Restoring volume from tar: {volume_name} ({len(tar_data)} bytes)")
+
+ # Create the volume if it doesn't exist
+ try:
+ self.client.volumes.get(volume_name)
+ logger.debug(f"Volume {volume_name} already exists, will overwrite data")
+ except NotFound:
+ logger.info(f"Creating new volume: {volume_name}")
+ self.client.volumes.create(volume_name)
+
+ # Create temporary container to restore volume data
+ temp_container_config = {
+ "image": "alpine:latest",
+ "command": "sleep 30",
+ "volumes": {volume_name: {"bind": "/volume_data", "mode": "rw"}},
+ "detach": True,
+ "remove": False,
+ "name": f"volume-restore-{volume_name}-temp",
+ }
+
+ temp_container = None
+ try:
+ # Ensure alpine image is available
+ try:
+ self.client.images.get("alpine:latest")
+ except NotFound:
+ logger.info("Pulling alpine:latest for volume restore...")
+ self.client.images.pull("alpine:latest")
+
+ # Create and start temporary container
+ temp_container = self.client.containers.run(**temp_container_config)
+ logger.debug(f"Created temporary restore container: {temp_container.name}")
+
+ # Clear existing volume contents first
+ logger.debug("Clearing existing volume contents...")
+ temp_container.exec_run("sh -c 'rm -rf /volume_data/* /volume_data/.*' || true")
+
+ # Extract tar archive into volume
+ logger.debug("Extracting tar data into volume...")
+ temp_container.put_archive("/", tar_data)
+
+ # Verify extraction worked
+ result = temp_container.exec_run("ls -la /volume_data/")
+ if result.exit_code == 0:
+ logger.debug(f"Volume contents after restore:\n{result.output.decode()}")
+ else:
+ logger.warning(f"Could not verify volume contents: {result.output.decode()}")
+
+ logger.info(f"Volume {volume_name} restored successfully")
+
+ finally:
+ # Clean up temporary container
+ if temp_container:
+ try:
+ temp_container.stop(timeout=5)
+ temp_container.remove()
+ logger.debug(
+ f"Cleaned up temporary restore container: {temp_container.name}"
+ )
+ except Exception as e:
+ logger.warning(f"Failed to clean up temporary restore container: {e}")
+
+ except DockerException as e:
+ raise SnapshotError(f"Failed to restore volume {volume_name}: {e}")
+ except Exception as e:
+ raise SnapshotError(f"Unexpected error restoring volume {volume_name}: {e}")
+
+ async def _restore_container_volumes(
+ self, snapshot_metadata: SnapshotMetadata, storage: Any
+ ) -> Dict[str, str]:
+ """
+ Restore all volumes for a container from storage.
+
+ Args:
+ snapshot_metadata: Metadata of the snapshot being restored
+ storage: Storage instance to load volume data from
+
+ Returns:
+ Dictionary mapping original volume names to restored volume names
+
+ Raises:
+ SnapshotError: If volume restore fails
+ """
+ volume_mappings: Dict[str, str] = {}
+
+ # Get volume information from original snapshot
+ volume_analysis = snapshot_metadata.agent_metadata.get("volume_analysis", {})
+ original_volumes = volume_analysis.get("named_volumes", [])
+
+ if not original_volumes:
+ logger.info("No named volumes to restore")
+ return volume_mappings
+
+ logger.info(
+ f"Restoring {len(original_volumes)} volumes from snapshot {snapshot_metadata.snapshot_id}"
+ )
+
+ # Get list of available volume files from storage
+ available_volumes = await storage.list_volume_files(snapshot_metadata.snapshot_id)
+
+ for volume_info in original_volumes:
+ original_volume_name = volume_info.get("name")
+ if not original_volume_name:
+ logger.warning(f"Skipping volume without name: {volume_info}")
+ continue
+
+ if original_volume_name not in available_volumes:
+ logger.warning(f"Volume data not found in storage: {original_volume_name}")
+ continue
+
+ try:
+ # Load volume tar data from storage
+ tar_data = await storage.load_volume_data(
+ snapshot_metadata.snapshot_id, original_volume_name
+ )
+
+ # Generate new volume name for restoration
+ # For now, we'll restore with the same name (could be made configurable)
+ restored_volume_name = original_volume_name
+
+ # Restore the volume
+ self._restore_volume_from_tar(restored_volume_name, tar_data)
+
+ volume_mappings[original_volume_name] = restored_volume_name
+ logger.info(
+ f"Successfully restored volume: {original_volume_name} → {restored_volume_name}"
+ )
+
+ except Exception as e:
+ logger.error(f"Failed to restore volume {original_volume_name}: {e}")
+ # Continue with other volumes, but track the failure
+ raise SnapshotError(f"Volume restore failed for {original_volume_name}: {e}")
+
+ logger.info(f"Volume restore completed: {len(volume_mappings)} volumes restored")
+ return volume_mappings
+
+ def _generate_image_tag(self, metadata: SnapshotMetadata) -> str:
+ """
+ Generate a Docker image tag for the snapshot.
+
+ Args:
+ metadata: Snapshot metadata
+
+ Returns:
+ Docker image tag string
+ """
+ timestamp = metadata.timestamp.strftime("%Y%m%d_%H%M%S")
+ # Docker tags must be lowercase and contain only valid characters
+ safe_name = metadata.container_name.lower().replace("_", "-")
+ return f"cua-snapshot/{safe_name}:{metadata.trigger.value}-{timestamp}"
+
+ def _get_container_info(self, container_id: str) -> Dict[str, Any]:
+ """
+ Get detailed information about a container.
+
+ Args:
+ container_id: ID or name of the container
+
+ Returns:
+ Dictionary with container information
+
+ Raises:
+ ContainerNotFoundError: If container doesn't exist
+ """
+ try:
+ container = self.client.containers.get(container_id)
+ return {
+ "id": container.id,
+ "name": container.name,
+ "status": container.status,
+ "image": container.image.id,
+ "labels": container.labels,
+ "created": container.attrs["Created"],
+ "config": container.attrs["Config"],
+ "mounts": container.attrs["Mounts"] if "Mounts" in container.attrs else [],
+ "network_settings": (
+ container.attrs["NetworkSettings"]
+ if "NetworkSettings" in container.attrs
+ else {}
+ ),
+ }
+ except NotFound:
+ raise ContainerNotFoundError(f"Container '{container_id}' not found")
+ except DockerException as e:
+ raise SnapshotError(f"Error getting container info: {e}")
+
+ async def validate_container(self, container_id: str) -> bool:
+ """
+ Validate that a container exists and can be snapshotted.
+
+ Args:
+ container_id: ID or name of the container
+
+ Returns:
+ True if container is valid for snapshotting
+ """
+ try:
+ container_info = self._get_container_info(container_id)
+
+ # Check if container is in a state that can be snapshotted
+ valid_statuses = ["running", "paused", "exited", "created"]
+ if container_info["status"] not in valid_statuses:
+ logger.warning(
+ f"Container {container_id} has status '{container_info['status']}', may not be snapshotable"
+ )
+ return False
+
+ return True
+ except ContainerNotFoundError:
+ return False
+ except Exception as e:
+ logger.error(f"Error validating container {container_id}: {e}")
+ return False
+
+ async def create_snapshot(
+ self, container_id: str, metadata: SnapshotMetadata
+ ) -> SnapshotMetadata:
+ """
+ Create a snapshot of the specified container using Docker commit.
+
+ Args:
+ container_id: ID or name of the container to snapshot
+ metadata: Metadata for the snapshot
+
+ Returns:
+ Updated metadata with storage information
+
+ Raises:
+ SnapshotError: If snapshot creation fails
+ """
+ logger.info(f"Creating snapshot for container {container_id}")
+
+ try:
+ # Validate container exists and get info
+ container_info = self._get_container_info(container_id)
+
+ # Analyze volumes attached to the container
+ volume_analysis = self._analyze_container_volumes(container_id)
+ logger.info(
+ f"Volume analysis: {volume_analysis['total_volumes']} named volumes, {volume_analysis['total_bind_mounts']} bind mounts"
+ )
+
+ # Update metadata with container information
+ metadata.container_id = container_info["id"]
+ metadata.container_name = container_info["name"]
+ metadata.status = SnapshotStatus.CREATING
+
+ # Generate image tag
+ image_tag = self._generate_image_tag(metadata)
+ metadata.image_tag = image_tag
+
+ # Get the container object
+ container = self.client.containers.get(container_id)
+
+ # Create commit message
+ commit_message = f"CUA Snapshot: {metadata.trigger.value}"
+ if metadata.description:
+ commit_message += f" - {metadata.description}"
+
+ # Create the snapshot using Docker commit
+ # This creates a new image from the container's current state
+ logger.info(f"Committing container {container_id} to image {image_tag}")
+
+ # Run the commit operation
+ image = container.commit(
+ repository=image_tag.split(":")[0],
+ tag=image_tag.split(":")[1],
+ message=commit_message,
+ changes=None, # Could be used for additional Dockerfile-like changes
+ )
+
+ # Update metadata with image information
+ metadata.image_id = image.id
+ metadata.status = SnapshotStatus.COMPLETED
+
+ # Backup volumes if any are attached
+ volume_backups = {}
+ if volume_analysis['total_volumes'] > 0:
+ logger.info(f"Backing up {volume_analysis['total_volumes']} named volumes...")
+ try:
+ volume_backups = self._backup_container_volumes(volume_analysis)
+ logger.info(f"Successfully backed up {len(volume_backups)} volumes")
+ except Exception as e:
+ logger.error(f"Volume backup failed: {e}")
+ # Continue with snapshot but log the failure
+ # We could make this configurable (fail vs continue)
+
+ # Calculate size
+ try:
+ # Get image size from Docker
+ size_bytes = await self.get_snapshot_size(metadata)
+ metadata.size_bytes = size_bytes
+ except Exception as e:
+ logger.warning(f"Could not determine snapshot size: {e}")
+
+ # Add Docker-specific metadata
+ metadata.labels.update(
+ {
+ "docker.image.id": image.id,
+ "docker.container.id": container_info["id"],
+ "docker.container.name": container_info["name"],
+ "docker.created": datetime.now().isoformat(),
+ "cua.snapshot.version": "1.0",
+ }
+ )
+
+ # Store original container configuration for restoration
+ metadata.agent_metadata.update(
+ {
+ "original_config": container_info["config"],
+ "original_mounts": container_info["mounts"],
+ "original_network_settings": container_info["network_settings"],
+ "original_image": container_info["image"],
+ "volume_analysis": volume_analysis,
+ "volume_backups": list(volume_backups.keys()) if volume_backups else [],
+ }
+ )
+
+ logger.info(
+ f"Successfully created snapshot {metadata.snapshot_id} for container {container_id}"
+ )
+
+ # Attach volume backup data to metadata for manager to store
+ # This is a temporary attribute, not persisted in metadata JSON
+ setattr(metadata, '_volume_backups', volume_backups)
+
+ return metadata
+
+ except ContainerNotFoundError:
+ metadata.status = SnapshotStatus.FAILED
+ raise
+ except DockerException as e:
+ metadata.status = SnapshotStatus.FAILED
+ raise SnapshotError(f"Docker error creating snapshot: {e}")
+ except Exception as e:
+ metadata.status = SnapshotStatus.FAILED
+ raise SnapshotError(f"Unexpected error creating snapshot: {e}")
+
+ async def restore_snapshot(
+ self, metadata: SnapshotMetadata, options: Optional[RestoreOptions] = None
+ ) -> str:
+ """
+ Restore a container from a snapshot.
+
+ Args:
+ metadata: Metadata of the snapshot to restore
+ options: Optional restore configuration
+
+ Returns:
+ ID of the restored container
+
+ Raises:
+ SnapshotError: If restore fails
+ """
+ logger.info(f"Restoring snapshot {metadata.snapshot_id}")
+
+ if not metadata.image_id:
+ raise SnapshotError("Snapshot has no image ID - cannot restore")
+
+ try:
+ # Set default options
+ if options is None:
+ options = RestoreOptions()
+
+ # Get the snapshot image
+ try:
+ image = self.client.images.get(metadata.image_id)
+ except NotFound:
+ raise SnapshotError(f"Snapshot image {metadata.image_id} not found")
+
+ # Prepare container configuration
+ container_name = (
+ options.new_container_name
+ or f"{metadata.container_name}_restored_{int(datetime.now().timestamp())}"
+ )
+
+ # Get original configuration from metadata
+ original_config = metadata.agent_metadata.get("original_config", {})
+ original_mounts = metadata.agent_metadata.get("original_mounts", [])
+
+ # Build run configuration
+ run_config = {
+ "name": container_name,
+ "detach": True,
+ }
+
+ # Apply environment overrides
+ env_vars = original_config.get("Env", [])
+ if options.environment_overrides:
+ # Convert list of "KEY=VALUE" to dict, apply overrides, convert back
+ env_dict = {}
+ for env_var in env_vars:
+ if "=" in env_var:
+ key, value = env_var.split("=", 1)
+ env_dict[key] = value
+
+ env_dict.update(options.environment_overrides)
+ run_config["environment"] = env_dict
+ else:
+ run_config["environment"] = env_vars
+
+ # Apply port mappings
+ if options.port_mappings:
+ run_config["ports"] = options.port_mappings
+ elif "ExposedPorts" in original_config:
+ # Try to preserve original port mappings if possible
+ run_config["ports"] = {
+ port: None for port in original_config["ExposedPorts"].keys()
+ }
+
+ # Handle volumes and mounts if preserving them
+ if options.preserve_volumes and original_mounts:
+ volume_binds = []
+
+ for mount in original_mounts:
+ if mount.get("Type") == "bind":
+ # Bind mounts - format: "host_path:container_path:mode"
+ mode = mount.get("Mode", "rw")
+ volume_binds.append(f"{mount['Source']}:{mount['Destination']}:{mode}")
+ elif mount.get("Type") == "volume":
+ # Named volumes - format: "volume_name:container_path:mode"
+ mode = mount.get("Mode", "rw")
+ volume_name = mount.get("Name", "")
+ if volume_name:
+ volume_binds.append(f"{volume_name}:{mount['Destination']}:{mode}")
+
+ if volume_binds:
+ run_config["volumes"] = volume_binds
+
+ # Apply working directory
+ if "WorkingDir" in original_config:
+ run_config["working_dir"] = original_config["WorkingDir"]
+
+ # Apply user
+ if "User" in original_config:
+ run_config["user"] = original_config["User"]
+
+ # Apply command and entrypoint
+ if "Cmd" in original_config and original_config["Cmd"]:
+ run_config["command"] = original_config["Cmd"]
+
+ if "Entrypoint" in original_config and original_config["Entrypoint"]:
+ run_config["entrypoint"] = original_config["Entrypoint"]
+
+ # Create and start the restored container
+ logger.info(
+ f"Creating restored container '{container_name}' from image {metadata.image_id}"
+ )
+
+ container = self.client.containers.run(image.id, **run_config)
+
+ # Update restoration tracking
+ metadata.restoration_count += 1
+
+ logger.info(
+ f"Successfully restored container {container.id} from snapshot {metadata.snapshot_id}"
+ )
+ return str(container.id)
+
+ except DockerException as e:
+ raise SnapshotError(f"Docker error restoring snapshot: {e}")
+ except Exception as e:
+ raise SnapshotError(f"Unexpected error restoring snapshot: {e}")
+
+ async def delete_snapshot(self, metadata: SnapshotMetadata) -> None:
+ """
+ Delete a snapshot and clean up its storage.
+
+ Args:
+ metadata: Metadata of the snapshot to delete
+
+ Raises:
+ SnapshotError: If deletion fails
+ """
+ logger.info(f"Deleting snapshot {metadata.snapshot_id}")
+
+ try:
+ if metadata.image_id:
+ try:
+ # Remove the Docker image
+ self.client.images.remove(metadata.image_id, force=True)
+ logger.info(f"Removed Docker image {metadata.image_id}")
+ except NotFound:
+ logger.warning(f"Image {metadata.image_id} not found during deletion")
+ except DockerException as e:
+ logger.error(f"Error removing Docker image: {e}")
+ # Don't raise here - we still want to clean up metadata
+
+ # Update status
+ metadata.status = SnapshotStatus.DELETED
+
+ except Exception as e:
+ raise SnapshotError(f"Error deleting snapshot: {e}")
+
+ async def get_snapshot_size(self, metadata: SnapshotMetadata) -> int:
+ """
+ Get the size of a snapshot in bytes.
+
+ Args:
+ metadata: Metadata of the snapshot
+
+ Returns:
+ Size in bytes
+ """
+ if not metadata.image_id:
+ return 0
+
+ try:
+ image = self.client.images.get(metadata.image_id)
+ # Docker image size is in the attrs
+ return int(image.attrs.get("Size", 0))
+ except NotFound:
+ return 0
+ except DockerException as e:
+ logger.error(f"Error getting snapshot size: {e}")
+ return 0
diff --git a/libs/python/snapshot-manager/src/snapshot_manager/storage.py b/libs/python/snapshot-manager/src/snapshot_manager/storage.py
new file mode 100644
index 00000000..8e3705cf
--- /dev/null
+++ b/libs/python/snapshot-manager/src/snapshot_manager/storage.py
@@ -0,0 +1,469 @@
+"""
+Storage implementations for snapshot metadata.
+
+Provides file-based storage for snapshot metadata with JSON serialization.
+"""
+
+import json
+import logging
+from datetime import datetime
+from pathlib import Path
+from typing import Any, Dict, List, Optional
+
+import aiofiles
+
+from .interfaces import SnapshotStorage, StorageError
+from .models import SnapshotMetadata
+
+logger = logging.getLogger(__name__)
+
+
+class FileSystemSnapshotStorage(SnapshotStorage):
+ """
+ File system-based storage for snapshot metadata.
+
+ Stores each snapshot's metadata as a JSON file in a structured
+ directory hierarchy for efficient access and management.
+ """
+
+ def __init__(self, base_path: str = ".snapshots"):
+ """
+ Initialize file system storage.
+
+ Args:
+ base_path: Base directory for storing snapshot metadata
+ """
+ self.base_path = Path(base_path)
+ self.metadata_dir = self.base_path / "metadata"
+ self.volumes_dir = self.base_path / "volumes"
+ self.index_file = self.base_path / "index.json"
+
+ # Create directory structure
+ self._ensure_directories()
+
+ def _ensure_directories(self) -> None:
+ """Ensure the storage directory structure exists."""
+ try:
+ self.base_path.mkdir(parents=True, exist_ok=True)
+ self.metadata_dir.mkdir(parents=True, exist_ok=True)
+ self.volumes_dir.mkdir(parents=True, exist_ok=True)
+ logger.debug(f"Storage directories ensured at {self.base_path}")
+ except OSError as e:
+ raise StorageError(f"Failed to create storage directories: {e}")
+
+ def _get_metadata_path(self, snapshot_id: str) -> Path:
+ """
+ Get the file path for a snapshot's metadata.
+
+ Args:
+ snapshot_id: ID of the snapshot
+
+ Returns:
+ Path to the metadata file
+ """
+ return self.metadata_dir / f"{snapshot_id}.json"
+
+ async def _load_index(self) -> Dict[str, Any]:
+ """
+ Load the snapshot index from disk.
+
+ Returns:
+ Dictionary containing the snapshot index
+ """
+ if not self.index_file.exists():
+ return {"snapshots": {}, "containers": {}, "last_updated": None}
+
+ try:
+ async with aiofiles.open(self.index_file, "r") as f:
+ content = await f.read()
+ return json.loads(content)
+ except (json.JSONDecodeError, OSError) as e:
+ logger.error(f"Error loading index: {e}")
+ return {"snapshots": {}, "containers": {}, "last_updated": None}
+
+ async def _save_index(self, index: Dict[str, Any]) -> None:
+ """
+ Save the snapshot index to disk.
+
+ Args:
+ index: Index dictionary to save
+ """
+ try:
+ index["last_updated"] = datetime.now().isoformat()
+ async with aiofiles.open(self.index_file, "w") as f:
+ await f.write(json.dumps(index, indent=2))
+ except OSError as e:
+ logger.error(f"Error saving index: {e}")
+ raise StorageError(f"Failed to save index: {e}")
+
+ async def _update_index(self, metadata: SnapshotMetadata, remove: bool = False) -> None:
+ """
+ Update the snapshot index with new or removed metadata.
+
+ Args:
+ metadata: Snapshot metadata to add/remove
+ remove: Whether to remove the entry (default: False = add)
+ """
+ index = await self._load_index()
+
+ if remove:
+ # Remove from index
+ index["snapshots"].pop(metadata.snapshot_id, None)
+
+ # Remove from container index
+ container_snapshots = index["containers"].get(metadata.container_id, [])
+ if metadata.snapshot_id in container_snapshots:
+ container_snapshots.remove(metadata.snapshot_id)
+ if not container_snapshots:
+ index["containers"].pop(metadata.container_id, None)
+ else:
+ index["containers"][metadata.container_id] = container_snapshots
+ else:
+ # Add to index
+ index["snapshots"][metadata.snapshot_id] = {
+ "container_id": metadata.container_id,
+ "container_name": metadata.container_name,
+ "timestamp": metadata.timestamp.isoformat(),
+ "trigger": metadata.trigger.value,
+ "status": metadata.status.value,
+ "size_bytes": metadata.size_bytes or 0,
+ }
+
+ # Add to container index
+ if metadata.container_id not in index["containers"]:
+ index["containers"][metadata.container_id] = []
+ if metadata.snapshot_id not in index["containers"][metadata.container_id]:
+ index["containers"][metadata.container_id].append(metadata.snapshot_id)
+
+ await self._save_index(index)
+
+ async def save_metadata(self, metadata: SnapshotMetadata) -> None:
+ """
+ Save snapshot metadata to persistent storage.
+
+ Args:
+ metadata: Metadata to save
+
+ Raises:
+ StorageError: If save operation fails
+ """
+ try:
+ metadata_path = self._get_metadata_path(metadata.snapshot_id)
+
+ # Convert to dictionary for JSON serialization
+ metadata_dict = metadata.model_dump()
+
+ # Ensure timestamp is serializable
+ if isinstance(metadata_dict.get("timestamp"), datetime):
+ metadata_dict["timestamp"] = metadata_dict["timestamp"].isoformat()
+
+ async with aiofiles.open(metadata_path, "w") as f:
+ await f.write(json.dumps(metadata_dict, indent=2))
+
+ # Update index
+ await self._update_index(metadata)
+
+ logger.debug(f"Saved metadata for snapshot {metadata.snapshot_id}")
+
+ except OSError as e:
+ raise StorageError(f"Failed to save metadata for {metadata.snapshot_id}: {e}")
+ except Exception as e:
+ raise StorageError(f"Unexpected error saving metadata: {e}")
+
+ async def load_metadata(self, snapshot_id: str) -> Optional[SnapshotMetadata]:
+ """
+ Load snapshot metadata by ID.
+
+ Args:
+ snapshot_id: ID of the snapshot
+
+ Returns:
+ Metadata if found, None otherwise
+ """
+ try:
+ metadata_path = self._get_metadata_path(snapshot_id)
+
+ if not metadata_path.exists():
+ return None
+
+ async with aiofiles.open(metadata_path, "r") as f:
+ content = await f.read()
+ metadata_dict = json.loads(content)
+
+ # Convert timestamp back to datetime if it's a string
+ if isinstance(metadata_dict.get("timestamp"), str):
+ metadata_dict["timestamp"] = datetime.fromisoformat(metadata_dict["timestamp"])
+
+ return SnapshotMetadata(**metadata_dict)
+
+ except (json.JSONDecodeError, OSError) as e:
+ logger.error(f"Error loading metadata for {snapshot_id}: {e}")
+ return None
+ except Exception as e:
+ logger.error(f"Unexpected error loading metadata for {snapshot_id}: {e}")
+ return None
+
+ async def list_snapshots(
+ self, container_id: Optional[str] = None, limit: Optional[int] = None
+ ) -> List[SnapshotMetadata]:
+ """
+ List snapshots, optionally filtered by container.
+
+ Args:
+ container_id: Optional filter by container ID
+ limit: Optional limit on number of results
+
+ Returns:
+ List of snapshot metadata, sorted by timestamp (newest first)
+ """
+ try:
+ index = await self._load_index()
+
+ if container_id:
+ # Get snapshots for specific container
+ snapshot_ids = index["containers"].get(container_id, [])
+ else:
+ # Get all snapshots
+ snapshot_ids = list(index["snapshots"].keys())
+
+ # Load metadata for each snapshot
+ snapshots = []
+ for snapshot_id in snapshot_ids:
+ metadata = await self.load_metadata(snapshot_id)
+ if metadata:
+ snapshots.append(metadata)
+
+ # Sort by timestamp (newest first)
+ snapshots.sort(key=lambda x: x.timestamp, reverse=True)
+
+ # Apply limit if specified
+ if limit:
+ snapshots = snapshots[:limit]
+
+ return snapshots
+
+ except Exception as e:
+ logger.error(f"Error listing snapshots: {e}")
+ return []
+
+ async def delete_metadata(self, snapshot_id: str) -> None:
+ """
+ Delete snapshot metadata from storage.
+
+ Args:
+ snapshot_id: ID of the snapshot to delete
+
+ Raises:
+ StorageError: If deletion fails
+ """
+ try:
+ # Load metadata first to get container_id for index update
+ metadata = await self.load_metadata(snapshot_id)
+
+ # Delete metadata file
+ metadata_path = self._get_metadata_path(snapshot_id)
+ if metadata_path.exists():
+ metadata_path.unlink()
+
+ # Update index if we had metadata
+ if metadata:
+ await self._update_index(metadata, remove=True)
+
+ logger.debug(f"Deleted metadata for snapshot {snapshot_id}")
+
+ except OSError as e:
+ raise StorageError(f"Failed to delete metadata for {snapshot_id}: {e}")
+ except Exception as e:
+ raise StorageError(f"Unexpected error deleting metadata: {e}")
+
+ async def update_metadata(self, metadata: SnapshotMetadata) -> None:
+ """
+ Update existing snapshot metadata.
+
+ Args:
+ metadata: Updated metadata
+
+ Raises:
+ StorageError: If update fails
+ """
+ # For file-based storage, update is the same as save
+ await self.save_metadata(metadata)
+
+ async def get_storage_stats(self) -> Dict[str, Any]:
+ """
+ Get storage statistics (total size, count, etc.).
+
+ Returns:
+ Dictionary with storage statistics
+ """
+ try:
+ index = await self._load_index()
+
+ total_snapshots = len(index["snapshots"])
+ total_size_bytes = sum(
+ snapshot_info.get("size_bytes", 0) for snapshot_info in index["snapshots"].values()
+ )
+
+ container_count = len(index["containers"])
+
+ # Calculate file system usage
+ metadata_size = 0
+ if self.metadata_dir.exists():
+ for metadata_file in self.metadata_dir.glob("*.json"):
+ metadata_size += metadata_file.stat().st_size
+
+ return {
+ "total_snapshots": total_snapshots,
+ "total_containers": container_count,
+ "total_size_bytes": total_size_bytes,
+ "total_size_gb": total_size_bytes / (1024**3),
+ "metadata_size_bytes": metadata_size,
+ "storage_path": str(self.base_path),
+ "last_updated": index.get("last_updated"),
+ }
+
+ except Exception as e:
+ logger.error(f"Error getting storage stats: {e}")
+ return {
+ "total_snapshots": 0,
+ "total_containers": 0,
+ "total_size_bytes": 0,
+ "total_size_gb": 0,
+ "metadata_size_bytes": 0,
+ "storage_path": str(self.base_path),
+ "last_updated": None,
+ "error": str(e),
+ }
+
+ async def cleanup_orphaned_metadata(self) -> int:
+ """
+ Remove metadata files that don't exist in the index.
+
+ Returns:
+ Number of orphaned files cleaned up
+ """
+ try:
+ index = await self._load_index()
+ indexed_ids = set(index["snapshots"].keys())
+
+ cleanup_count = 0
+ if self.metadata_dir.exists():
+ for metadata_file in self.metadata_dir.glob("*.json"):
+ snapshot_id = metadata_file.stem
+ if snapshot_id not in indexed_ids:
+ metadata_file.unlink()
+ cleanup_count += 1
+ logger.info(f"Cleaned up orphaned metadata file: {snapshot_id}")
+
+ return cleanup_count
+
+ except Exception as e:
+ logger.error(f"Error during cleanup: {e}")
+ return 0
+
+ async def save_volume_data(self, snapshot_id: str, volume_name: str, tar_data: bytes) -> None:
+ """
+ Save volume tar data to storage.
+
+ Args:
+ snapshot_id: ID of the snapshot this volume belongs to
+ volume_name: Name of the volume
+ tar_data: Tar archive data as bytes
+
+ Raises:
+ StorageError: If saving fails
+ """
+ try:
+ volume_file = self.volumes_dir / f"{snapshot_id}_{volume_name}.tar"
+
+ async with aiofiles.open(volume_file, "wb") as f:
+ await f.write(tar_data)
+
+ logger.debug(f"Saved volume data: {volume_file} ({len(tar_data)} bytes)")
+
+ except Exception as e:
+ raise StorageError(f"Failed to save volume data {volume_name} for {snapshot_id}: {e}")
+
+ async def load_volume_data(self, snapshot_id: str, volume_name: str) -> bytes:
+ """
+ Load volume tar data from storage.
+
+ Args:
+ snapshot_id: ID of the snapshot
+ volume_name: Name of the volume
+
+ Returns:
+ Tar archive data as bytes
+
+ Raises:
+ StorageError: If loading fails
+ """
+ try:
+ volume_file = self.volumes_dir / f"{snapshot_id}_{volume_name}.tar"
+
+ if not volume_file.exists():
+ raise StorageError(f"Volume data not found: {volume_file}")
+
+ async with aiofiles.open(volume_file, "rb") as f:
+ tar_data = await f.read()
+
+ logger.debug(f"Loaded volume data: {volume_file} ({len(tar_data)} bytes)")
+ return tar_data
+
+ except Exception as e:
+ raise StorageError(f"Failed to load volume data {volume_name} for {snapshot_id}: {e}")
+
+ async def list_volume_files(self, snapshot_id: str) -> List[str]:
+ """
+ List volume files for a snapshot.
+
+ Args:
+ snapshot_id: ID of the snapshot
+
+ Returns:
+ List of volume names that have stored data
+ """
+ try:
+ volume_files = list(self.volumes_dir.glob(f"{snapshot_id}_*.tar"))
+ volume_names = []
+
+ for volume_file in volume_files:
+ # Extract volume name from filename: "snapshot_id_volume_name.tar"
+ filename = volume_file.stem # Remove .tar extension
+ if filename.startswith(f"{snapshot_id}_"):
+ volume_name = filename[len(f"{snapshot_id}_") :]
+ volume_names.append(volume_name)
+
+ return volume_names
+
+ except Exception as e:
+ logger.error(f"Failed to list volume files for {snapshot_id}: {e}")
+ return []
+
+ async def delete_volume_data(self, snapshot_id: str, volume_name: Optional[str] = None) -> None:
+ """
+ Delete volume data from storage.
+
+ Args:
+ snapshot_id: ID of the snapshot
+ volume_name: Name of specific volume to delete, or None to delete all volumes for snapshot
+
+ Raises:
+ StorageError: If deletion fails
+ """
+ try:
+ if volume_name:
+ # Delete specific volume
+ volume_file = self.volumes_dir / f"{snapshot_id}_{volume_name}.tar"
+ if volume_file.exists():
+ volume_file.unlink()
+ logger.debug(f"Deleted volume data: {volume_file}")
+ else:
+ # Delete all volumes for snapshot
+ volume_files = list(self.volumes_dir.glob(f"{snapshot_id}_*.tar"))
+ for volume_file in volume_files:
+ volume_file.unlink()
+ logger.debug(f"Deleted volume data: {volume_file}")
+
+ except Exception as e:
+ raise StorageError(f"Failed to delete volume data for {snapshot_id}: {e}")
diff --git a/libs/python/snapshot-manager/tests/test_snapshot_manager.py b/libs/python/snapshot-manager/tests/test_snapshot_manager.py
new file mode 100644
index 00000000..cff26653
--- /dev/null
+++ b/libs/python/snapshot-manager/tests/test_snapshot_manager.py
@@ -0,0 +1,273 @@
+"""
+Critical test cases for the snapshot manager system.
+"""
+
+import shutil
+import tempfile
+from unittest.mock import AsyncMock, Mock
+
+import docker
+import pytest
+
+from snapshot_manager.manager import SnapshotManager
+from snapshot_manager.models import SnapshotConfig, SnapshotStatus, SnapshotTrigger
+from snapshot_manager.providers.docker_provider import DockerSnapshotProvider
+from snapshot_manager.storage import FileSystemSnapshotStorage
+
+
+class TestSnapshotManager:
+ """Test cases for the SnapshotManager class."""
+
+ @pytest.fixture
+ def temp_storage(self):
+ """Create a temporary storage directory for testing."""
+ temp_dir = tempfile.mkdtemp()
+ yield temp_dir
+ shutil.rmtree(temp_dir)
+
+ @pytest.fixture
+ def mock_docker_client(self):
+ """Mock Docker client for testing."""
+ mock_client = Mock(spec=docker.DockerClient)
+ mock_client.ping.return_value = True
+
+ # Mock container
+ mock_container = Mock()
+ mock_container.id = "test_container_id"
+ mock_container.name = "test_container"
+ mock_container.status = "running"
+ mock_container.image.id = "test_image_id"
+ mock_container.labels = {}
+ mock_container.attrs = {
+ "Created": "2024-01-01T00:00:00Z",
+ "Config": {"Env": [], "WorkingDir": "/app"},
+ "Mounts": [],
+ "NetworkSettings": {},
+ }
+
+ # Mock commit operation
+ mock_image = Mock()
+ mock_image.id = "snapshot_image_id"
+ mock_image.attrs = {"Size": 1024 * 1024 * 100} # 100MB
+ mock_container.commit.return_value = mock_image
+
+ mock_client.containers.get.return_value = mock_container
+ mock_client.containers.run.return_value = mock_container
+ mock_client.images.get.return_value = mock_image
+
+ return mock_client
+
+ @pytest.fixture
+ def snapshot_manager(self, temp_storage, mock_docker_client):
+ """Create a SnapshotManager instance for testing."""
+ config = SnapshotConfig(
+ storage_path=temp_storage, max_snapshots_per_container=3, max_total_snapshots=10
+ )
+
+ provider = DockerSnapshotProvider(docker_client=mock_docker_client)
+ storage = FileSystemSnapshotStorage(base_path=temp_storage)
+
+ return SnapshotManager(provider=provider, storage=storage, config=config)
+
+ @pytest.mark.asyncio
+ async def test_create_snapshot(self, snapshot_manager):
+ """Test creating a snapshot."""
+ metadata = await snapshot_manager.create_snapshot(
+ container_id="test_container",
+ trigger=SnapshotTrigger.MANUAL,
+ description="Test snapshot",
+ )
+
+ assert metadata.snapshot_id is not None
+ assert metadata.container_name == "test_container"
+ assert metadata.trigger == SnapshotTrigger.MANUAL
+ assert metadata.status == SnapshotStatus.COMPLETED
+ assert metadata.description == "Test snapshot"
+
+ @pytest.mark.asyncio
+ async def test_list_snapshots(self, snapshot_manager):
+ """Test listing snapshots."""
+ # Create a few snapshots
+ await snapshot_manager.create_snapshot("test_container", SnapshotTrigger.MANUAL)
+ await snapshot_manager.create_snapshot("test_container", SnapshotTrigger.RUN_START)
+
+ # List all snapshots
+ snapshots = await snapshot_manager.list_snapshots()
+ assert len(snapshots) == 2
+
+ # List snapshots for specific container
+ container_snapshots = await snapshot_manager.list_snapshots(
+ container_id="test_container_id"
+ )
+ assert len(container_snapshots) == 2
+
+ @pytest.mark.asyncio
+ async def test_restore_snapshot(self, snapshot_manager):
+ """Test restoring a snapshot."""
+ # Create a snapshot
+ metadata = await snapshot_manager.create_snapshot("test_container", SnapshotTrigger.MANUAL)
+
+ # Restore it
+ from snapshot_manager.models import RestoreOptions
+
+ options = RestoreOptions(new_container_name="restored_container")
+
+ restored_id = await snapshot_manager.restore_snapshot(metadata.snapshot_id, options)
+ assert restored_id is not None
+
+ @pytest.mark.asyncio
+ async def test_snapshot_limits(self, snapshot_manager):
+ """Test that snapshot limits are enforced."""
+ # Create first snapshot to establish the container_id
+ first_snapshot = await snapshot_manager.create_snapshot(
+ "test_container", SnapshotTrigger.MANUAL, description="First snapshot"
+ )
+
+ # Now use the resolved container_id for subsequent snapshots
+ container_id = first_snapshot.container_id
+
+ # Create more snapshots using the actual container_id
+ for i in range(3): # Create 3 more (total will be 4, limit is 3)
+ await snapshot_manager.create_snapshot(
+ container_id, SnapshotTrigger.MANUAL, description=f"Snapshot {i+2}"
+ )
+
+ # Should only have 3 snapshots due to limit enforcement
+ snapshots = await snapshot_manager.list_snapshots()
+ assert len(snapshots) == 3
+
+ @pytest.mark.asyncio
+ async def test_delete_snapshot(self, snapshot_manager):
+ """Test deleting individual snapshots."""
+ # Create a snapshot
+ metadata = await snapshot_manager.create_snapshot(
+ "test_container", SnapshotTrigger.MANUAL, description="To be deleted"
+ )
+
+ # Verify it exists
+ snapshots = await snapshot_manager.list_snapshots()
+ assert len(snapshots) == 1
+
+ # Delete it
+ await snapshot_manager.delete_snapshot(metadata.snapshot_id)
+
+ # Verify it's gone
+ snapshots = await snapshot_manager.list_snapshots()
+ assert len(snapshots) == 0
+
+ @pytest.mark.asyncio
+ async def test_cleanup_old_snapshots(self, snapshot_manager):
+ """Test time-based cleanup of old snapshots."""
+ from datetime import datetime, timedelta
+ from unittest.mock import patch
+
+ # Create snapshots with different ages by mocking timestamp
+ snapshots_created = []
+
+ # Create an "old" snapshot (10 days ago)
+ with patch("snapshot_manager.models.datetime") as mock_datetime:
+ old_time = datetime.now() - timedelta(days=10)
+ mock_datetime.now.return_value = old_time
+
+ old_snapshot = await snapshot_manager.create_snapshot(
+ "test_container", SnapshotTrigger.MANUAL, description="Old snapshot"
+ )
+ # Manually update the timestamp in storage
+ old_snapshot.timestamp = old_time
+ await snapshot_manager.storage.update_metadata(old_snapshot)
+ snapshots_created.append(old_snapshot)
+
+ # Create a recent snapshot (1 day ago)
+ recent_snapshot = await snapshot_manager.create_snapshot(
+ "test_container", SnapshotTrigger.MANUAL, description="Recent snapshot"
+ )
+ snapshots_created.append(recent_snapshot)
+
+ # Verify we have 2 snapshots
+ all_snapshots = await snapshot_manager.list_snapshots()
+ assert len(all_snapshots) == 2
+
+ # Cleanup snapshots older than 5 days
+ cleaned_count = await snapshot_manager.cleanup_old_snapshots(max_age_days=5)
+
+ # Should have cleaned up 1 old snapshot
+ assert cleaned_count == 1
+
+ # Verify only recent snapshot remains
+ remaining_snapshots = await snapshot_manager.list_snapshots()
+ assert len(remaining_snapshots) == 1
+ assert remaining_snapshots[0].snapshot_id == recent_snapshot.snapshot_id
+
+
+class TestDockerSnapshotProvider:
+ """Critical tests for DockerSnapshotProvider."""
+
+ @pytest.fixture
+ def mock_docker_client(self):
+ """Mock Docker client."""
+ mock_client = Mock(spec=docker.DockerClient)
+ mock_client.ping.return_value = True
+ return mock_client
+
+ @pytest.fixture
+ def docker_provider(self, mock_docker_client):
+ """Create a DockerSnapshotProvider for testing."""
+ return DockerSnapshotProvider(docker_client=mock_docker_client)
+
+ @pytest.mark.asyncio
+ async def test_validate_container_success(self, docker_provider, mock_docker_client):
+ """Test successful container validation."""
+ mock_container = Mock()
+ mock_container.id = "test_id"
+ mock_container.name = "test_name"
+ mock_container.status = "running"
+ mock_container.image.id = "test_image"
+ mock_container.labels = {}
+ mock_container.attrs = {"Created": "2024-01-01", "Config": {}, "Mounts": []}
+
+ mock_docker_client.containers.get.return_value = mock_container
+ is_valid = await docker_provider.validate_container("test_container")
+ assert is_valid is True
+
+ @pytest.mark.asyncio
+ async def test_validate_container_not_found(self, docker_provider, mock_docker_client):
+ """Test container validation when container doesn't exist."""
+ from docker.errors import NotFound
+
+ mock_docker_client.containers.get.side_effect = NotFound("Container not found")
+ is_valid = await docker_provider.validate_container("nonexistent_container")
+ assert is_valid is False
+
+
+class TestSnapshotCallback:
+ """Critical tests for SnapshotCallback."""
+
+ @pytest.fixture
+ def mock_snapshot_manager(self):
+ """Mock SnapshotManager for testing."""
+ manager = Mock(spec=SnapshotManager)
+ manager.should_create_snapshot = AsyncMock(return_value=True)
+ manager.create_snapshot = AsyncMock()
+ manager.config = SnapshotConfig() # Add config property for callback
+ return manager
+
+ @pytest.fixture
+ def snapshot_callback(self, mock_snapshot_manager):
+ """Create a SnapshotCallback for testing."""
+ from snapshot_manager.callback import SnapshotCallback
+
+ return SnapshotCallback(snapshot_manager=mock_snapshot_manager)
+
+ @pytest.mark.asyncio
+ async def test_on_run_start(self, snapshot_callback, mock_snapshot_manager):
+ """Test run start callback creates snapshot."""
+ kwargs = {"container_id": "test_container"}
+ await snapshot_callback.on_run_start(kwargs, [])
+
+ mock_snapshot_manager.create_snapshot.assert_called_once()
+ call_args = mock_snapshot_manager.create_snapshot.call_args
+ assert call_args[1]["trigger"] == SnapshotTrigger.RUN_START
+
+
+if __name__ == "__main__":
+ pytest.main([__file__])
diff --git a/libs/python/snapshot-manager/tests/test_volume_support.py b/libs/python/snapshot-manager/tests/test_volume_support.py
new file mode 100644
index 00000000..d70028df
--- /dev/null
+++ b/libs/python/snapshot-manager/tests/test_volume_support.py
@@ -0,0 +1,292 @@
+"""
+Test volume support functionality in the snapshot manager.
+
+Tests volume detection, backup, storage, restore, and bind mount warnings.
+"""
+
+import tempfile
+from unittest.mock import Mock, patch
+import pytest
+
+import docker
+from snapshot_manager.providers.docker_provider import DockerSnapshotProvider
+from snapshot_manager.storage import FileSystemSnapshotStorage
+from snapshot_manager.models import SnapshotMetadata, SnapshotTrigger
+
+
+class TestVolumeSupport:
+ """Test volume support functionality."""
+
+ @pytest.fixture
+ def temp_storage(self):
+ """Create temporary storage for testing."""
+ with tempfile.TemporaryDirectory() as temp_dir:
+ yield temp_dir
+
+ @pytest.fixture
+ def mock_docker_client(self):
+ """Mock Docker client for volume testing."""
+ mock_client = Mock(spec=docker.DockerClient)
+ mock_client.ping.return_value = True
+
+ # Mock container with volumes
+ mock_container = Mock()
+ mock_container.id = "test_container_id"
+ mock_container.name = "test_container"
+ mock_container.attrs = {
+ "Mounts": [
+ {
+ "Type": "volume",
+ "Name": "test_named_volume",
+ "Source": "/var/lib/docker/volumes/test_named_volume/_data",
+ "Destination": "/app/data",
+ "Mode": "rw",
+ "RW": True,
+ "Propagation": "",
+ },
+ {
+ "Type": "bind",
+ "Source": "/host/path",
+ "Destination": "/app/bind",
+ "Mode": "rw",
+ "RW": True,
+ "Propagation": "rprivate",
+ },
+ ]
+ }
+
+ # Mock volume operations
+ mock_volume = Mock()
+ mock_volume.name = "test_named_volume"
+ mock_volume.attrs = {"Driver": "local"}
+
+ # Mock temporary container for volume operations
+ mock_temp_container = Mock()
+ mock_temp_container.name = "volume-backup-test_named_volume-temp"
+
+ # Mock tar stream data
+ mock_temp_container.get_archive.return_value = (iter([b"fake_tar_data"]), None)
+ mock_temp_container.exec_run.return_value = Mock(exit_code=0, output=b"fake_output")
+ mock_temp_container.stop = Mock()
+ mock_temp_container.remove = Mock()
+ mock_temp_container.put_archive = Mock()
+
+ mock_client.containers.get.return_value = mock_container
+ mock_client.containers.run.return_value = mock_temp_container
+ mock_client.volumes.get.return_value = mock_volume
+ mock_client.volumes.create.return_value = mock_volume
+ mock_client.images.get.return_value = Mock() # Alpine image exists
+
+ return mock_client
+
+ @pytest.fixture
+ def docker_provider(self, mock_docker_client):
+ """Create DockerSnapshotProvider with mocked client."""
+ return DockerSnapshotProvider(docker_client=mock_docker_client)
+
+ @pytest.fixture
+ def storage(self, temp_storage):
+ """Create FileSystemSnapshotStorage."""
+ return FileSystemSnapshotStorage(base_path=temp_storage)
+
+ def test_volume_detection(self, docker_provider):
+ """Test volume detection functionality."""
+ # Test volume analysis
+ analysis = docker_provider._analyze_container_volumes("test_container")
+
+ # Should detect one named volume and one bind mount
+ assert analysis["total_volumes"] == 1
+ assert analysis["total_bind_mounts"] == 1
+
+ # Check named volume details
+ named_volumes = analysis["named_volumes"]
+ assert len(named_volumes) == 1
+ assert named_volumes[0]["name"] == "test_named_volume"
+ assert named_volumes[0]["destination"] == "/app/data"
+ assert named_volumes[0]["type"] == "volume"
+
+ # Check bind mount details
+ bind_mounts = analysis["bind_mounts"]
+ assert len(bind_mounts) == 1
+ assert bind_mounts[0]["source"] == "/host/path"
+ assert bind_mounts[0]["destination"] == "/app/bind"
+ assert bind_mounts[0]["type"] == "bind"
+
+ def test_volume_backup(self, docker_provider):
+ """Test volume backup functionality."""
+ # Test single volume backup
+ tar_data = docker_provider._backup_named_volume_as_tar("test_named_volume")
+ assert tar_data == b"fake_tar_data"
+
+ # Test full container volume backup
+ analysis = docker_provider._analyze_container_volumes("test_container")
+
+ # Mock the backup method to return expected data for the full test
+ with patch.object(
+ docker_provider, "_backup_named_volume_as_tar", return_value=b"fake_tar_data"
+ ):
+ volume_backups = docker_provider._backup_container_volumes(analysis)
+
+ # Should only backup named volumes, not bind mounts
+ assert len(volume_backups) == 1
+ assert "test_named_volume" in volume_backups
+ assert volume_backups["test_named_volume"] == b"fake_tar_data"
+
+ @pytest.mark.asyncio
+ async def test_volume_storage(self, storage):
+ """Test volume storage operations."""
+ snapshot_id = "test_snapshot_123"
+ volume_name = "test_volume"
+ test_data = b"test volume data"
+
+ # Test save
+ await storage.save_volume_data(snapshot_id, volume_name, test_data)
+
+ # Test load
+ loaded_data = await storage.load_volume_data(snapshot_id, volume_name)
+ assert loaded_data == test_data
+
+ # Test list
+ volume_files = await storage.list_volume_files(snapshot_id)
+ assert volume_files == [volume_name]
+
+ # Test delete specific
+ await storage.delete_volume_data(snapshot_id, volume_name)
+ volume_files = await storage.list_volume_files(snapshot_id)
+ assert volume_files == []
+
+ def test_volume_restore(self, docker_provider):
+ """Test volume restore functionality."""
+ test_data = b"restore_test_data"
+
+ # Mock volume not found initially to test creation
+ from docker.errors import NotFound
+
+ docker_provider.client.volumes.get.side_effect = [NotFound("Volume not found"), Mock()]
+
+ # Test volume restore
+ docker_provider._restore_volume_from_tar("restored_volume", test_data)
+
+ # Verify volume creation and container operations were called
+ docker_provider.client.volumes.create.assert_called_with("restored_volume")
+ docker_provider.client.containers.run.assert_called()
+
+ @pytest.mark.asyncio
+ async def test_full_container_volume_restore(self, docker_provider, storage):
+ """Test complete container volume restore workflow."""
+ # Setup test data
+ snapshot_id = "test_snapshot_456"
+ volume_name = "app_data"
+ test_tar_data = b"complete_restore_test"
+
+ # Save volume data to storage
+ await storage.save_volume_data(snapshot_id, volume_name, test_tar_data)
+
+ # Create snapshot metadata with volume information
+ snapshot_metadata = SnapshotMetadata(
+ snapshot_id=snapshot_id,
+ container_id="test_container",
+ container_name="test_container",
+ trigger=SnapshotTrigger.MANUAL,
+ agent_metadata={
+ "volume_analysis": {
+ "named_volumes": [
+ {"name": volume_name, "destination": "/app/data", "type": "volume"}
+ ],
+ "bind_mounts": [],
+ "total_volumes": 1,
+ "total_bind_mounts": 0,
+ }
+ },
+ )
+
+ # Test volume restore
+ volume_mappings = await docker_provider._restore_container_volumes(
+ snapshot_metadata, storage
+ )
+
+ # Verify restore mapping
+ assert volume_name in volume_mappings
+ assert volume_mappings[volume_name] == volume_name
+
+ def test_bind_mount_warnings(self, docker_provider, caplog):
+ """Test that bind mount warnings are properly logged."""
+ import logging
+
+ # Set up logging to capture warnings
+ caplog.set_level(logging.WARNING)
+
+ # Analyze container with bind mounts
+ analysis = docker_provider._analyze_container_volumes("test_container")
+
+ # Trigger volume backup which should generate warnings
+ docker_provider._backup_container_volumes(analysis)
+
+ # Check that bind mount warnings were logged
+ warning_messages = [
+ record.message for record in caplog.records if record.levelno >= logging.WARNING
+ ]
+
+ # Should have warnings about bind mounts
+ bind_mount_warnings = [msg for msg in warning_messages if "bind mount" in msg.lower()]
+ assert len(bind_mount_warnings) > 0
+
+ # Should mention the specific bind mount path
+ path_mentioned = any("/host/path" in msg for msg in bind_mount_warnings)
+ assert path_mentioned
+
+ def test_volume_error_handling(self, docker_provider):
+ """Test error handling in volume operations."""
+ from docker.errors import NotFound
+ from snapshot_manager.interfaces import SnapshotError
+
+ # Test backup of non-existent volume
+ docker_provider.client.volumes.get.side_effect = NotFound("Volume not found")
+
+ with pytest.raises(SnapshotError, match="Volume .* not found"):
+ docker_provider._backup_named_volume_as_tar("nonexistent_volume")
+
+ @pytest.mark.asyncio
+ async def test_storage_error_handling(self, storage):
+ """Test storage error handling."""
+ from snapshot_manager.interfaces import StorageError
+
+ # Test loading non-existent volume data
+ with pytest.raises(StorageError, match="Volume data not found"):
+ await storage.load_volume_data("nonexistent_snapshot", "nonexistent_volume")
+
+ def test_volume_analysis_empty_container(self, mock_docker_client):
+ """Test volume analysis for container with no volumes."""
+ # Mock container with no mounts
+ mock_container = Mock()
+ mock_container.attrs = {"Mounts": []}
+ mock_docker_client.containers.get.return_value = mock_container
+
+ provider = DockerSnapshotProvider(docker_client=mock_docker_client)
+ analysis = provider._analyze_container_volumes("empty_container")
+
+ assert analysis["total_volumes"] == 0
+ assert analysis["total_bind_mounts"] == 0
+ assert analysis["named_volumes"] == []
+ assert analysis["bind_mounts"] == []
+
+ @pytest.mark.asyncio
+ async def test_storage_directory_structure(self, temp_storage):
+ """Test that storage creates proper directory structure."""
+ storage = FileSystemSnapshotStorage(base_path=temp_storage)
+
+ # Save some volume data
+ await storage.save_volume_data("test_snap", "test_vol", b"test_data")
+
+ # Check directory structure
+ from pathlib import Path
+
+ base_path = Path(temp_storage)
+
+ assert (base_path / "metadata").exists()
+ assert (base_path / "volumes").exists()
+ assert (base_path / "volumes" / "test_snap_test_vol.tar").exists()
+
+
+if __name__ == "__main__":
+ pytest.main([__file__])
diff --git a/pyproject.toml b/pyproject.toml
index 2330fbe9..57ba4bfe 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,7 @@ description = "CUA (Computer Use Agent) mono-repo"
license = { text = "MIT" }
name = "cua-workspace"
readme = "README.md"
-requires-python = ">=3.11"
+requires-python = ">=3.12"
version = "0.1.0"
[project.urls]