diff --git a/.claude/skills/unity-mcp-skill/SKILL.md b/.claude/skills/unity-mcp-skill/SKILL.md index bda21f49b..d93a6b0a1 100644 --- a/.claude/skills/unity-mcp-skill/SKILL.md +++ b/.claude/skills/unity-mcp-skill/SKILL.md @@ -55,16 +55,40 @@ batch_execute( **Max 25 commands per batch by default (configurable in Unity MCP Tools window, max 100).** Use `fail_fast=True` for dependent operations. -### 3. Use `screenshot` in manage_scene to Verify Visual Results +### 3. Use Screenshots to Verify Visual Results ```python -# Via manage_scene -manage_scene(action="screenshot") # Returns base64 image +# Basic screenshot (saves to Assets/, returns file path only) +manage_scene(action="screenshot") -# After creating/modifying objects, verify visually: -# 1. Create objects -# 2. capture screenshot -# 3. Analyze if result matches intent +# Inline screenshot (returns base64 PNG directly to the AI) +manage_scene(action="screenshot", include_image=True) + +# Use a specific camera and cap resolution for smaller payloads +manage_scene(action="screenshot", camera="MainCamera", include_image=True, max_resolution=512) + +# Batch surround: captures front/back/left/right/top/bird_eye around the scene +manage_scene(action="screenshot", batch="surround", max_resolution=256) + +# Batch surround centered on a specific object +manage_scene(action="screenshot", batch="surround", look_at="Player", max_resolution=256) + +# Positioned screenshot: place a temp camera and capture in one call +manage_scene(action="screenshot", look_at="Player", view_position=[0, 10, -10], max_resolution=512) +``` + +**Best practices for AI scene understanding:** +- Use `include_image=True` when you need to *see* the scene, not just save a file. +- Use `batch="surround"` for a comprehensive overview (6 angles, one command). +- Use `look_at`/`view_position` to capture from a specific viewpoint without needing a scene camera. +- Keep `max_resolution` at 256–512 to balance quality vs. token cost. +- Combine with `look_at` on `manage_gameobject` to orient a game camera before capturing. + +```python +# Agentic camera loop: point, shoot, analyze +manage_gameobject(action="look_at", target="MainCamera", look_at_target="Player") +manage_scene(action="screenshot", camera="MainCamera", include_image=True, max_resolution=512) +# → Analyze image, decide next action ``` ### 4. Check Console After Major Changes @@ -134,7 +158,7 @@ uri="file:///full/path/to/file.cs" | **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control | | **Testing** | `run_tests`, `get_test_job` | Unity Test Framework | | **Batch** | `batch_execute` | Parallel/bulk operations | -| **UI** | `batch_execute` with `manage_gameobject` + `manage_components` | Canvas, Panel, Button, Text, Slider, Toggle, Input Field (see [UI workflows](references/workflows.md#ui-creation-workflows)) | +| **UI** | `batch_execute` with `manage_gameobject` + `manage_components` | Canvas, Panel, Button, Text, Slider, Toggle, Input Field. **Read `mcpforunity://project/info` first** to detect uGUI/TMP/Input System availability. (see [UI workflows](references/workflows.md#ui-creation-workflows)) | ## Common Workflows diff --git a/.claude/skills/unity-mcp-skill/references/tools-reference.md b/.claude/skills/unity-mcp-skill/references/tools-reference.md index 0cb251155..e071dd02c 100644 --- a/.claude/skills/unity-mcp-skill/references/tools-reference.md +++ b/.claude/skills/unity-mcp-skill/references/tools-reference.md @@ -17,6 +17,34 @@ Complete reference for all MCP tools. Each tool includes parameters, types, and --- +## Project Info Resource + +Read `mcpforunity://project/info` to detect project capabilities before making assumptions about UI, input, or rendering setup. + +**Returned fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `projectRoot` | string | Absolute path to project root | +| `projectName` | string | Project folder name | +| `unityVersion` | string | e.g. `"2022.3.20f1"` | +| `platform` | string | Active build target e.g. `"StandaloneWindows64"` | +| `assetsPath` | string | Absolute path to Assets folder | +| `renderPipeline` | string | `"BuiltIn"`, `"Universal"`, `"HighDefinition"`, or `"Custom"` | +| `activeInputHandler` | string | `"Old"`, `"New"`, or `"Both"` | +| `packages.ugui` | bool | `com.unity.ugui` installed (Canvas, Image, Button, etc.) | +| `packages.textmeshpro` | bool | `com.unity.textmeshpro` installed (TMP_Text, TMP_InputField) | +| `packages.inputsystem` | bool | `com.unity.inputsystem` installed (InputAction, PlayerInput) | + +**Key decision points:** + +- **UI system**: If `packages.ugui` is true, use Canvas + uGUI components. UI Toolkit (UIDocument/UXML) is built-in since Unity 2021+ but has no MCP tool support yet. +- **Text**: If `packages.textmeshpro` is true, use `TextMeshProUGUI` instead of legacy `Text`. +- **Input**: Use `activeInputHandler` to decide EventSystem module — `StandaloneInputModule` (Old) vs `InputSystemUIInputModule` (New). See [workflows.md — Input System](workflows.md#input-system-old-vs-new). +- **Shaders**: Use `renderPipeline` to pick correct shader names — `Standard` (BuiltIn) vs `Universal Render Pipeline/Lit` (URP) vs `HDRP/Lit` (HDRP). + +--- + ## Infrastructure Tools ### batch_execute @@ -66,7 +94,7 @@ refresh_unity( ### manage_scene -Scene CRUD operations and hierarchy queries. +Scene CRUD operations, hierarchy queries, screenshots, and scene view control. ```python # Get hierarchy (paginated) @@ -78,8 +106,47 @@ manage_scene( include_transform=False # bool - include local transforms ) -# Screenshot -manage_scene(action="screenshot") # Returns base64 PNG +# Screenshot (file only — saves to Assets/) +manage_scene(action="screenshot") + +# Screenshot with inline image (base64 PNG returned to AI) +manage_scene( + action="screenshot", + camera="MainCamera", # str, optional - camera name, path, or instance ID + include_image=True, # bool, default False - return base64 PNG inline + max_resolution=512 # int, optional - downscale cap (default 640) +) + +# Batch surround (6 angles around scene bounds, no file saved) +manage_scene( + action="screenshot", + batch="surround", # str - "surround" for 6-angle capture + max_resolution=256 # int - keep low for batch (6 images) +) +# Returns: front, back, left, right, top, bird_eye views + +# Batch surround centered on a specific target +manage_scene( + action="screenshot", + batch="surround", + look_at="Player", # str|int|list[float] - center surround on this target + max_resolution=256 +) + +# Positioned screenshot (temp camera at viewpoint, no file saved) +manage_scene( + action="screenshot", + look_at="Enemy", # str|int|list[float] - target to aim at + view_position=[0, 10, -10], # list[float], optional - camera position + view_rotation=[45, 0, 0], # list[float], optional - euler angles (overrides look_at aim) + max_resolution=512 +) + +# Frame scene view on target +manage_scene( + action="scene_view_frame", + scene_view_target="Player" # str|int - GO name, path, or instance ID to frame +) # Other actions manage_scene(action="get_active") # Current scene info @@ -166,6 +233,14 @@ manage_gameobject( distance=5.0, world_space=True ) + +# Look at target (rotates GO to face a point or another GO) +manage_gameobject( + action="look_at", + target="MainCamera", # the GO to rotate + look_at_target="Player", # str (GO name/path) or list[float] world position + look_at_up=[0, 1, 0] # optional up vector, default [0,1,0] +) ``` ### manage_components @@ -207,6 +282,24 @@ manage_components( "localScale": [2, 2, 2] } ) + +# Set object reference property (reference another GameObject by name) +manage_components( + action="set_property", + target="GameManager", + component_type="GameManagerScript", + property="targetObjects", + value=[{"name": "Flower_1"}, {"name": "Flower_2"}, {"name": "Bee_1"}] +) + +# Object reference formats supported: +# - {"name": "ObjectName"} → Find GameObject in scene by name +# - {"instanceID": 12345} → Direct instance ID reference +# - {"guid": "abc123..."} → Asset GUID reference +# - {"path": "Assets/..."} → Asset path reference +# - "Assets/Prefabs/My.prefab" → String shorthand for asset paths +# - "ObjectName" → String shorthand for scene name lookup +# - 12345 → Integer shorthand for instanceID ``` --- @@ -449,7 +542,10 @@ manage_material( action="set_renderer_color", target="MyCube", color=[1, 0, 0, 1], - mode="instance" # "shared"|"instance"|"property_block" + mode="create_unique" # Creates a unique .mat asset per object (persistent) + # Other modes: "property_block" (default, not persistent), + # "shared" (mutates shared material — avoid for primitives), + # "instance" (runtime only, not persistent) ) ``` diff --git a/.claude/skills/unity-mcp-skill/references/workflows.md b/.claude/skills/unity-mcp-skill/references/workflows.md index 545b5c518..5ecfb8d54 100644 --- a/.claude/skills/unity-mcp-skill/references/workflows.md +++ b/.claude/skills/unity-mcp-skill/references/workflows.md @@ -51,6 +51,73 @@ if editor_state["is_compiling"]: --- +## Scene Generator Build Workflow + +### Fresh Scene Before Building + +**Always start a generated scene build with `manage_scene(action="create")`** to get a clean empty scene. This avoids conflicts with existing default objects (Camera, Light) that would cause "already exists" errors when the execution plan tries to create its own. + +```python +# Step 0: Create fresh empty scene (replaces current scene entirely) +manage_scene(action="create", name="MyGeneratedScene", path="Assets/Scenes/") + +# Now proceed with the phased execution plan... +# Phase 1: Environment (camera, lights) — no conflicts +# Phase 2: Objects (GameObjects) +# Phase 3: Materials +# etc. +``` + +### Wiring Object References Between Components + +After creating scripts and attaching components, use `set_property` to wire cross-references between GameObjects. Use the `{"name": "ObjectName"}` format to reference scene objects by name: + +```python +# Wire a list of target GameObjects into a script's serialized field +manage_components( + action="set_property", + target="BeeManager", + component_type="BeeManagerScript", + property="targetObjects", + value=[{"name": "Flower_1"}, {"name": "Flower_2"}, {"name": "Flower_3"}] +) +``` + +### Physics Requirements for Trigger-Based Interactions + +When scripts use `OnTriggerEnter` / `OnTriggerStay` / `OnTriggerExit`, at least one of the two colliding objects **must** have a `Rigidbody` component. Common pattern: + +```python +# Moving objects (bees, players) need Rigidbody for triggers to fire +batch_execute(commands=[ + {"tool": "manage_components", "params": { + "action": "add", "target": "Bee_1", "component_type": "Rigidbody" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "Bee_1", + "component_type": "Rigidbody", + "properties": {"useGravity": false, "isKinematic": true} + }} +]) +``` + +### Script Overwrites with `manage_script(action="update")` + +When a generated script needs to be rewritten (e.g., to add auto-wiring logic), use `update` instead of deleting and recreating: + +```python +manage_script( + action="update", + path="Assets/Scripts/MyScript.cs", + contents="using UnityEngine;\n\npublic class MyScript : MonoBehaviour { ... }" +) +# Then refresh and check console +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) +read_console(types=["error"], count=10) +``` + +--- + ## Scene Creation Workflows ### Create Complete Scene from Scratch @@ -498,11 +565,83 @@ Unity UI (Canvas-based UGUI) requires specific component hierarchies. Use `batch > **Template warning:** This section is a skill template library, not a guaranteed source of truth. Examples may be inaccurate for your Unity version, package setup, or project conventions. > **Use safely:** -> 1. Validate component/property names against the current project. -> 2. Prefer targeting by instance ID or full path over generic names. -> 3. Assume complex controls (Slider/Toggle/TMP Input) may need extra reference wiring. +> 1. **Always read `mcpforunity://project/info` first** to detect installed packages and input system. +> 2. Validate component/property names against the current project. +> 3. Prefer targeting by instance ID or full path over generic names. > 4. Treat numeric enum values as placeholders and verify before reuse. +### Step 0: Detect Project UI Capabilities + +**Before creating any UI**, read project info to determine which packages and input system are available. + +```python +# Read mcpforunity://project/info — returns: +# { +# "renderPipeline": "BuiltIn" | "Universal" | "HighDefinition" | "Custom", +# "activeInputHandler": "Old" | "New" | "Both", +# "packages": { +# "ugui": true/false, — com.unity.ugui (Canvas, Image, Button, etc.) +# "textmeshpro": true/false, — com.unity.textmeshpro (TextMeshProUGUI) +# "inputsystem": true/false — com.unity.inputsystem (new Input System) +# } +# } +``` + +**Decision matrix:** + +| project_info field | Value | What to use | +|---|---|---| +| `packages.ugui` | `true` | Canvas-based UI (Image, Button, etc.) | +| `packages.textmeshpro` | `true` | `TextMeshProUGUI` for text | +| `packages.textmeshpro` | `false` | `UnityEngine.UI.Text` (legacy, lower quality) | +| `activeInputHandler` | `"Old"` | `StandaloneInputModule` for EventSystem | +| `activeInputHandler` | `"New"` | `InputSystemUIInputModule` for EventSystem | +| `activeInputHandler` | `"Both"` | Either works; prefer `InputSystemUIInputModule` for UI | + +### RectTransform Sizing (Critical for All UI Children) + +Every GameObject under a Canvas gets a `RectTransform` instead of `Transform`. **Without setting anchor/size, UI elements default to zero size and won't be visible.** Use `set_property` on `RectTransform`: + +```python +# Stretch to fill parent (common for panels/backgrounds) +{"tool": "manage_components", "params": { + "action": "set_property", "target": "MyPanel", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 0], # bottom-left corner + "anchorMax": [1, 1], # top-right corner + "sizeDelta": [0, 0], # no extra size beyond anchors + "anchoredPosition": [0, 0] # centered on anchors + } +}} + +# Fixed-size centered element (e.g. 300x50 button) +{"tool": "manage_components", "params": { + "action": "set_property", "target": "MyButton", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], + "anchorMax": [0.5, 0.5], + "sizeDelta": [300, 50], + "anchoredPosition": [0, 0] + } +}} + +# Top-anchored bar (e.g. health bar at top of screen) +{"tool": "manage_components", "params": { + "action": "set_property", "target": "TopBar", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 1], # left-top + "anchorMax": [1, 1], # right-top (stretch horizontally) + "sizeDelta": [0, 60], # 60px tall, full width + "anchoredPosition": [0, -30] # offset down by half height + } +}} +``` + +> **Note:** Vector2 properties accept both `[x, y]` array format and `{"x": ..., "y": ...}` object format. + ### Create Canvas (Foundation for All UI) Every UI element must be under a Canvas. A Canvas requires three components: `Canvas`, `CanvasScaler`, and `GraphicRaycaster`. @@ -541,9 +680,10 @@ batch_execute(fail_fast=True, commands=[ ### Create EventSystem (Required Once Per Scene for UI Interaction) -If no EventSystem exists in the scene, buttons and other interactive UI elements won't respond to input. Create one alongside your first Canvas. +If no EventSystem exists in the scene, buttons and other interactive UI elements won't respond to input. Create one alongside your first Canvas. **Check `project_info.activeInputHandler` to pick the correct input module.** ```python +# For activeInputHandler == "New" or "Both" (project has Input System package): batch_execute(fail_fast=True, commands=[ {"tool": "manage_gameobject", "params": { "action": "create", "name": "EventSystem" @@ -557,9 +697,22 @@ batch_execute(fail_fast=True, commands=[ "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule" }} ]) -``` -> **Note:** For projects using legacy Input Manager instead of Input System, use `"component_type": "UnityEngine.EventSystems.StandaloneInputModule"` instead. +# For activeInputHandler == "Old" (legacy Input Manager only): +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_gameobject", "params": { + "action": "create", "name": "EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.StandaloneInputModule" + }} +]) +``` ### Create Panel (Background Container) @@ -573,18 +726,26 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "MenuPanel", "component_type": "Image" }}, - # Set semi-transparent dark background {"tool": "manage_components", "params": { "action": "set_property", "target": "MenuPanel", "component_type": "Image", "property": "color", "value": [0.1, 0.1, 0.1, 0.8] + }}, + # Size the panel (stretch to 60% of canvas, centered) + {"tool": "manage_components", "params": { + "action": "set_property", "target": "MenuPanel", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.2, 0.1], "anchorMax": [0.8, 0.9], + "sizeDelta": [0, 0], "anchoredPosition": [0, 0] + } }} ]) ``` ### Create Text (TextMeshPro) -TextMeshProUGUI automatically adds a RectTransform when added to a child of a Canvas. +TextMeshProUGUI automatically adds a RectTransform when added to a child of a Canvas. If `packages.textmeshpro` is `false`, use `UnityEngine.UI.Text` instead. ```python batch_execute(fail_fast=True, commands=[ @@ -604,6 +765,14 @@ batch_execute(fail_fast=True, commands=[ "alignment": 514, "color": [1, 1, 1, 1] } + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "TitleText", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 0.8], "anchorMax": [1, 1], + "sizeDelta": [0, 0], "anchoredPosition": [0, 0] + } }} ]) ``` @@ -616,7 +785,6 @@ A Button needs an `Image` (visual) + `Button` (interaction) on the parent, and a ```python batch_execute(fail_fast=True, commands=[ - # Button container with Image + Button components {"tool": "manage_gameobject", "params": { "action": "create", "name": "StartButton", "parent": "MenuPanel" }}, @@ -631,7 +799,15 @@ batch_execute(fail_fast=True, commands=[ "component_type": "Image", "property": "color", "value": [0.2, 0.6, 1.0, 1.0] }}, - # Child text label + {"tool": "manage_components", "params": { + "action": "set_property", "target": "StartButton", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [300, 60], "anchoredPosition": [0, 0] + } + }}, + # Child text label (stretches to fill button) {"tool": "manage_gameobject", "params": { "action": "create", "name": "StartButton_Label", "parent": "StartButton" }}, @@ -643,17 +819,25 @@ batch_execute(fail_fast=True, commands=[ "action": "set_property", "target": "StartButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Start Game", "fontSize": 24, "alignment": 514} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "StartButton_Label", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 0], "anchorMax": [1, 1], + "sizeDelta": [0, 0], "anchoredPosition": [0, 0] + } }} ]) ``` -### Create Slider +### Create Slider (With Reference Wiring) -A Slider requires a specific hierarchy: the slider root, a background, a fill area with fill, and a handle area with handle. +A Slider requires a specific hierarchy and **must have its `fillRect` and `handleRect` references wired** to function. ```python +# Step 1: Create hierarchy batch_execute(fail_fast=True, commands=[ - # Slider root {"tool": "manage_gameobject", "params": { "action": "create", "name": "HealthSlider", "parent": "MainCanvas" }}, @@ -663,49 +847,95 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "HealthSlider", "component_type": "Image" }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "HealthSlider", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [400, 30], "anchoredPosition": [0, 0] + } + }}, # Background {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Background", "parent": "HealthSlider" + "action": "create", "name": "SliderBG", "parent": "HealthSlider" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Background", "component_type": "Image" + "action": "add", "target": "SliderBG", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Background", - "component_type": "Image", "property": "color", - "value": [0.3, 0.3, 0.3, 1.0] + "action": "set_property", "target": "SliderBG", + "component_type": "Image", "property": "color", "value": [0.3, 0.3, 0.3, 1.0] + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SliderBG", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, # Fill Area + Fill {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Fill Area", "parent": "HealthSlider" + "action": "create", "name": "FillArea", "parent": "HealthSlider" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "FillArea", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Fill", "parent": "Fill Area" + "action": "create", "name": "SliderFill", "parent": "FillArea" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Fill", "component_type": "Image" + "action": "add", "target": "SliderFill", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Fill", - "component_type": "Image", "property": "color", - "value": [0.2, 0.8, 0.2, 1.0] + "action": "set_property", "target": "SliderFill", + "component_type": "Image", "property": "color", "value": [0.2, 0.8, 0.2, 1.0] + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SliderFill", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, # Handle Area + Handle {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Handle Slide Area", "parent": "HealthSlider" + "action": "create", "name": "HandleArea", "parent": "HealthSlider" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "HandleArea", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Handle", "parent": "Handle Slide Area" + "action": "create", "name": "SliderHandle", "parent": "HandleArea" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "SliderHandle", "component_type": "Image" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SliderHandle", + "component_type": "RectTransform", + "properties": {"anchorMin": [0.5, 0], "anchorMax": [0.5, 1], "sizeDelta": [20, 0]} + }} +]) + +# Step 2: Wire Slider references (CRITICAL — slider won't work without this) +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_components", "params": { + "action": "set_property", "target": "HealthSlider", + "component_type": "Slider", "property": "fillRect", + "value": {"name": "SliderFill"} }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Handle", "component_type": "Image" + "action": "set_property", "target": "HealthSlider", + "component_type": "Slider", "property": "handleRect", + "value": {"name": "SliderHandle"} }} ]) ``` -### Create Input Field (TextMeshPro) +### Create Input Field (With Reference Wiring) ```python +# Step 1: Create hierarchy batch_execute(fail_fast=True, commands=[ {"tool": "manage_gameobject", "params": { "action": "create", "name": "NameInput", "parent": "MenuPanel" @@ -717,39 +947,78 @@ batch_execute(fail_fast=True, commands=[ "action": "add", "target": "NameInput", "component_type": "TMP_InputField" }}, - # Text area child + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [400, 50], "anchoredPosition": [0, 0] + } + }}, + # Text Area child (clips text to input bounds) {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Text Area", "parent": "NameInput" + "action": "create", "name": "InputTextArea", "parent": "NameInput" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "InputTextArea", "component_type": "RectMask2D" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Text Area", - "component_type": "RectMask2D" + "action": "set_property", "target": "InputTextArea", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [-16, -8]} }}, # Placeholder {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Placeholder", "parent": "Text Area" + "action": "create", "name": "InputPlaceholder", "parent": "InputTextArea" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Placeholder", - "component_type": "TextMeshProUGUI" + "action": "add", "target": "InputPlaceholder", "component_type": "TextMeshProUGUI" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Placeholder", + "action": "set_property", "target": "InputPlaceholder", "component_type": "TextMeshProUGUI", "properties": {"text": "Enter name...", "fontStyle": 2, "color": [0.5, 0.5, 0.5, 0.5]} }}, - # Actual text + {"tool": "manage_components", "params": { + "action": "set_property", "target": "InputPlaceholder", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} + }}, + # Actual text display {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Text", "parent": "Text Area" + "action": "create", "name": "InputText", "parent": "InputTextArea" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Text", - "component_type": "TextMeshProUGUI" + "action": "add", "target": "InputText", "component_type": "TextMeshProUGUI" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "InputText", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} + }} +]) + +# Step 2: Wire TMP_InputField references (CRITICAL) +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "TMP_InputField", "property": "textViewport", + "value": {"name": "InputTextArea"} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "TMP_InputField", "property": "textComponent", + "value": {"name": "InputText"} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "TMP_InputField", "property": "placeholder", + "value": {"name": "InputPlaceholder"} }} ]) ``` -### Create Toggle (Checkbox) +### Create Toggle (With Reference Wiring) ```python batch_execute(fail_fast=True, commands=[ @@ -759,41 +1028,72 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "SoundToggle", "component_type": "Toggle" }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SoundToggle", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [200, 30], "anchoredPosition": [0, 0] + } + }}, # Background box {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Background", "parent": "SoundToggle" + "action": "create", "name": "ToggleBG", "parent": "SoundToggle" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "ToggleBG", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Background", "component_type": "Image" + "action": "set_property", "target": "ToggleBG", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0.5], "anchorMax": [0, 0.5], "sizeDelta": [26, 26], "anchoredPosition": [13, 0]} }}, # Checkmark {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Checkmark", "parent": "Background" + "action": "create", "name": "ToggleCheckmark", "parent": "ToggleBG" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "ToggleCheckmark", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Checkmark", "component_type": "Image" + "action": "set_property", "target": "ToggleCheckmark", + "component_type": "RectTransform", + "properties": {"anchorMin": [0.1, 0.1], "anchorMax": [0.9, 0.9], "sizeDelta": [0, 0]} }}, # Label {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Label", "parent": "SoundToggle" + "action": "create", "name": "ToggleLabel", "parent": "SoundToggle" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Label", "component_type": "TextMeshProUGUI" + "action": "add", "target": "ToggleLabel", "component_type": "TextMeshProUGUI" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Label", + "action": "set_property", "target": "ToggleLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Sound Effects", "fontSize": 18, "alignment": 513} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "ToggleLabel", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [-35, 0], "anchoredPosition": [17.5, 0]} + }} +]) + +# Wire Toggle references (CRITICAL) +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SoundToggle", + "component_type": "Toggle", "property": "graphic", + "value": {"name": "ToggleCheckmark"} }} ]) ``` ### Add Layout Group (Vertical/Horizontal/Grid) -Layout groups auto-arrange child elements. Add to any container. +Layout groups auto-arrange child elements, so you can skip manual RectTransform positioning for children. ```python -# Vertical layout for a menu panel batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "MenuPanel", @@ -804,12 +1104,12 @@ batch_execute(fail_fast=True, commands=[ "component_type": "VerticalLayoutGroup", "properties": { "spacing": 10, - "childAlignment": 1, + "childAlignment": 4, "childForceExpandWidth": True, - "childForceExpandHeight": False + "childForceExpandHeight": False, + "padding": {"left": 20, "right": 20, "top": 20, "bottom": 20} } }}, - # Add ContentSizeFitter to auto-resize {"tool": "manage_components", "params": { "action": "add", "target": "MenuPanel", "component_type": "ContentSizeFitter" @@ -817,9 +1117,7 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "set_property", "target": "MenuPanel", "component_type": "ContentSizeFitter", - "properties": { - "verticalFit": 2 - } + "properties": { "verticalFit": 2 } }} ]) ``` @@ -829,7 +1127,7 @@ batch_execute(fail_fast=True, commands=[ ### Complete Example: Main Menu Screen -Combines multiple templates into a full menu screen in two batch calls (default 25 command limit per batch, configurable in Unity MCP Tools window up to 100). +Combines multiple templates into a full menu screen in two batch calls (default 25 command limit per batch, configurable in Unity MCP Tools window up to 100). **Assumes `project_info` has been read and `activeInputHandler` is known.** ```python # Batch 1: Canvas + EventSystem + Panel + Title @@ -841,14 +1139,15 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": {"action": "add", "target": "MenuCanvas", "component_type": "GraphicRaycaster"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuCanvas", "component_type": "Canvas", "property": "renderMode", "value": 0}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuCanvas", "component_type": "CanvasScaler", "properties": {"uiScaleMode": 1, "referenceResolution": [1920, 1080]}}}, - # EventSystem + # EventSystem — use StandaloneInputModule OR InputSystemUIInputModule based on project_info {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}}, {"tool": "manage_components", "params": {"action": "add", "target": "EventSystem", "component_type": "UnityEngine.EventSystems.EventSystem"}}, {"tool": "manage_components", "params": {"action": "add", "target": "EventSystem", "component_type": "UnityEngine.EventSystems.StandaloneInputModule"}}, - # Panel + # Panel (centered, 60% width) {"tool": "manage_gameobject", "params": {"action": "create", "name": "MenuPanel", "parent": "MenuCanvas"}}, {"tool": "manage_components", "params": {"action": "add", "target": "MenuPanel", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "Image", "property": "color", "value": [0.1, 0.1, 0.15, 0.9]}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "RectTransform", "properties": {"anchorMin": [0.2, 0.15], "anchorMax": [0.8, 0.85], "sizeDelta": [0, 0]}}}, {"tool": "manage_components", "params": {"action": "add", "target": "MenuPanel", "component_type": "VerticalLayoutGroup"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "VerticalLayoutGroup", "properties": {"spacing": 20, "childAlignment": 4, "childForceExpandWidth": True, "childForceExpandHeight": False}}}, # Title @@ -864,25 +1163,28 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton", "component_type": "Button"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayButton", "component_type": "Image", "property": "color", "value": [0.2, 0.6, 1.0, 1.0]}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "PlayButton_Label", "parent": "PlayButton"}}, - {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI"}}, - {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Play", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_gameobject", "params": {"action": "create", "name": "PlayLabel", "parent": "PlayButton"}}, + {"tool": "manage_components", "params": {"action": "add", "target": "PlayLabel", "component_type": "TextMeshProUGUI"}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Play", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}}, # Settings Button {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsButton", "parent": "MenuPanel"}}, {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton", "component_type": "Button"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsButton", "component_type": "Image", "property": "color", "value": [0.3, 0.3, 0.35, 1.0]}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsButton_Label", "parent": "SettingsButton"}}, - {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI"}}, - {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Settings", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsLabel", "parent": "SettingsButton"}}, + {"tool": "manage_components", "params": {"action": "add", "target": "SettingsLabel", "component_type": "TextMeshProUGUI"}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Settings", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}}, # Quit Button {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitButton", "parent": "MenuPanel"}}, {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton", "component_type": "Button"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitButton", "component_type": "Image", "property": "color", "value": [0.8, 0.2, 0.2, 1.0]}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitButton_Label", "parent": "QuitButton"}}, - {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI"}}, - {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Quit", "fontSize": 32, "alignment": 514}}} + {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitLabel", "parent": "QuitButton"}}, + {"tool": "manage_components", "params": {"action": "add", "target": "QuitLabel", "component_type": "TextMeshProUGUI"}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Quit", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}} ]) ``` @@ -891,17 +1193,130 @@ batch_execute(fail_fast=True, commands=[ | UI Element | Required Components | Notes | | ---------- | ------------------- | ----- | | **Canvas** | Canvas + CanvasScaler + GraphicRaycaster | Root for all UI. One per screen. | -| **EventSystem** | EventSystem + StandaloneInputModule (or InputSystemUIInputModule) | One per scene. Required for interaction. | -| **Panel** | Image | Container. Set color for background. | -| **Text** | TextMeshProUGUI | Auto-adds RectTransform under Canvas. | -| **Button** | Image + Button + child(TextMeshProUGUI) | Image = visual, Button = click handler. | -| **Image** | Image | Set sprite property for custom graphics. | -| **Slider** | Slider + Image + children(Background, Fill Area/Fill, Handle Slide Area/Handle) | Complex hierarchy. | -| **Toggle** | Toggle + children(Background/Checkmark, Label) | Checkbox/radio button. | -| **Input Field** | Image + TMP_InputField + children(Text Area/Placeholder/Text) | Text input. | -| **Scroll View** | ScrollRect + Image + children(Viewport/Content, Scrollbar) | Scrollable container. | -| **Dropdown** | Image + TMP_Dropdown + children(Label, Arrow, Template) | Selection menu. | -| **Layout Group** | VerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroup | Add to any container to auto-arrange children. | +| **EventSystem** | EventSystem + input module (see below) | One per scene. Required for interaction. | +| **Panel** | Image + RectTransform sizing | Container. Set color for background. | +| **Text** | TextMeshProUGUI (or Text if no TMP) + RectTransform | Check `packages.textmeshpro`. | +| **Button** | Image + Button + child(TextMeshProUGUI) + RectTransform | Image = visual, Button = click handler. | +| **Slider** | Slider + Image + children + **wire fillRect/handleRect** | Won't function without wiring. | +| **Toggle** | Toggle + children + **wire graphic** | Wire checkmark Image to `graphic`. | +| **Input Field** | Image + TMP_InputField + children + **wire textViewport/textComponent/placeholder** | Won't function without wiring. | +| **Layout Group** | VerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroup | Auto-arranges children; skip manual RectTransform on children. | + +--- + +## Input System: Old vs New + +Unity has two input systems that affect UI interaction, script input handling, and EventSystem configuration. **Always check `project_info.activeInputHandler` before creating EventSystems or writing input code.** + +### Detection + +```python +# Read mcpforunity://project/info +# activeInputHandler: "Old" | "New" | "Both" +# packages.inputsystem: true/false (whether com.unity.inputsystem is installed) +``` + +### EventSystem — Old Input Manager + +Used when `activeInputHandler` is `"Old"`. Uses `StandaloneInputModule` which reads from `Input.GetAxis()` / `Input.GetButton()`. + +```python +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.StandaloneInputModule" + }} +]) +``` + +Script pattern (old Input Manager): + +```csharp +// Input.GetAxis / Input.GetKey — works with old Input Manager +void Update() +{ + float h = Input.GetAxis("Horizontal"); + float v = Input.GetAxis("Vertical"); + transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime); + + if (Input.GetKeyDown(KeyCode.Space)) + Jump(); + + if (Input.GetMouseButtonDown(0)) + Fire(); +} +``` + +### EventSystem — New Input System + +Used when `activeInputHandler` is `"New"` or `"Both"`. Uses `InputSystemUIInputModule` from the `com.unity.inputsystem` package. + +```python +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule" + }} +]) +``` + +Script pattern (new Input System with `PlayerInput` component): + +```csharp +using UnityEngine; +using UnityEngine.InputSystem; + +public class PlayerController : MonoBehaviour +{ + public float speed = 5f; + private Vector2 moveInput; + + // Called by PlayerInput component via SendMessages or UnityEvents + public void OnMove(InputValue value) + { + moveInput = value.Get(); + } + + public void OnJump(InputValue value) + { + if (value.isPressed) + Jump(); + } + + void Update() + { + Vector3 move = new Vector3(moveInput.x, 0, moveInput.y); + transform.Translate(move * speed * Time.deltaTime); + } +} +``` + +### When `activeInputHandler` is `"Both"` + +Both systems are active simultaneously. For UI, prefer `InputSystemUIInputModule`. For gameplay scripts, either approach works — `Input.GetAxis()` still functions alongside the new Input System. + +```python +# UI: use new Input System module +{"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule" +}} + +# Gameplay scripts: Input.GetAxis() still works in "Both" mode +# But prefer the new Input System for consistency +``` + +> **Gotcha:** Adding `StandaloneInputModule` when `activeInputHandler` is `"New"` will cause a runtime error. Always check first. --- diff --git a/.gitignore b/.gitignore index dc5e443fb..6cd591730 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ coverage.xml # Virtual environments .venv +# Environment files (API keys) +.env + # Unity Editor *.unitypackage *.asset diff --git a/MCPForUnity/Editor/Helpers/ComponentOps.cs b/MCPForUnity/Editor/Helpers/ComponentOps.cs index 5b19bb8a0..e4e456ac4 100644 --- a/MCPForUnity/Editor/Helpers/ComponentOps.cs +++ b/MCPForUnity/Editor/Helpers/ComponentOps.cs @@ -617,7 +617,13 @@ private static bool SetObjectReference(SerializedProperty prop, JToken value, ou return true; } - error = "Object reference must contain 'instanceID', 'guid', or 'path'."; + var nameToken = jObj["name"]; + if (nameToken != null) + { + return ResolveSceneObjectByName(prop, nameToken.ToString(), out error); + } + + error = "Object reference must contain 'instanceID', 'guid', 'path', or 'name'."; return false; } @@ -636,14 +642,65 @@ private static bool SetObjectReference(SerializedProperty prop, JToken value, ou prop.objectReferenceValue = resolved; return true; } - error = $"Cannot resolve object reference from string '{strVal}'."; - return false; + + // Fall back to scene hierarchy lookup by name. + return ResolveSceneObjectByName(prop, strVal, out error); } error = $"Unsupported object reference format: {value.Type}."; return false; } + /// + /// Resolves a scene GameObject by name and assigns it (or a component on it) + /// to a SerializedProperty. Uses GameObjectLookup for robust search + /// including inactive objects and prefab stage support. + /// + private static bool ResolveSceneObjectByName(SerializedProperty prop, string name, out string error) + { + error = null; + if (string.IsNullOrWhiteSpace(name)) + { + error = "Cannot resolve object reference from empty name."; + return false; + } + + var ids = GameObjectLookup.SearchGameObjects( + GameObjectLookup.SearchMethod.ByName, name, includeInactive: true, maxResults: 1); + + if (ids.Count == 0) + { + error = $"No GameObject named '{name}' found in scene."; + return false; + } + + var go = GameObjectLookup.FindById(ids[0]); + if (go == null) + { + error = $"GameObject '{name}' found but could not be resolved."; + return false; + } + + // If the property accepts a GameObject directly, assign it. + prop.objectReferenceValue = go; + if (prop.objectReferenceValue != null) + return true; + + // The field type may expect a specific Component (e.g. Transform, Rigidbody). + // Try each component on the GameObject until one is accepted. + var components = go.GetComponents(); + foreach (var comp in components) + { + if (comp == null) continue; + prop.objectReferenceValue = comp; + if (prop.objectReferenceValue != null) + return true; + } + + error = $"GameObject '{name}' found but no compatible component for property type."; + return false; + } + /// /// Finds a child SerializedProperty by name, falling back to underscore-insensitive matching. /// The batch_execute transport can strip underscores from JSON keys diff --git a/MCPForUnity/Editor/Helpers/RenderPipelineUtility.cs b/MCPForUnity/Editor/Helpers/RenderPipelineUtility.cs index 2065d1738..502dc36b1 100644 --- a/MCPForUnity/Editor/Helpers/RenderPipelineUtility.cs +++ b/MCPForUnity/Editor/Helpers/RenderPipelineUtility.cs @@ -24,11 +24,18 @@ internal enum VFXComponentType } private static Dictionary s_DefaultVFXMaterials = new Dictionary(); + private static Dictionary s_DefaultSceneMaterials = new Dictionary(); private static readonly string[] BuiltInLitShaders = { "Standard", "Legacy Shaders/Diffuse" }; private static readonly string[] BuiltInUnlitShaders = { "Unlit/Color", "Unlit/Texture" }; + private static readonly string[] BuiltInParticleShaders = { "Particles/Standard Unlit", "Particles/Alpha Blended", "Particles/Additive" }; private static readonly string[] UrpLitShaders = { "Universal Render Pipeline/Lit", "Universal Render Pipeline/Simple Lit" }; private static readonly string[] UrpUnlitShaders = { "Universal Render Pipeline/Unlit" }; + private static readonly string[] UrpParticleShaders = { + "Universal Render Pipeline/Particles/Unlit", + "Universal Render Pipeline/Particles/Simple Lit", + "Universal Render Pipeline/Particles/Lit", + }; private static readonly string[] HdrpLitShaders = { "HDRP/Lit", "High Definition Render Pipeline/Lit" }; private static readonly string[] HdrpUnlitShaders = { "HDRP/Unlit", "High Definition Render Pipeline/Unlit" }; @@ -170,8 +177,8 @@ private static void WarnIfPipelineMismatch(string shaderName, PipelineKind activ var lowerName = shaderName.ToLowerInvariant(); bool shaderLooksUrp = lowerName.Contains("universal render pipeline") || lowerName.Contains("urp/"); bool shaderLooksHdrp = lowerName.Contains("high definition render pipeline") || lowerName.Contains("hdrp/"); - bool shaderLooksBuiltin = lowerName.Contains("standard") || lowerName.Contains("legacy shaders/"); bool shaderLooksSrp = shaderLooksUrp || shaderLooksHdrp; + bool shaderLooksBuiltin = LooksLikeBuiltInShader(lowerName, shaderLooksSrp); switch (activePipeline) { @@ -204,6 +211,76 @@ private static void WarnIfPipelineMismatch(string shaderName, PipelineKind activ } } + internal static bool IsMaterialInvalidForActivePipeline(Material material, out string reason) + { + reason = null; + if (material == null) + { + reason = "missing_material"; + return true; + } + + Shader shader = material.shader; + if (shader == null) + { + reason = "missing_shader"; + return true; + } + + if (IsErrorShader(shader)) + { + reason = "error_shader"; + return true; + } + + var pipeline = GetActivePipeline(); + if (IsPipelineMismatch(shader.name, pipeline)) + { + reason = "pipeline_mismatch"; + return true; + } + + return false; + } + + private static bool IsErrorShader(Shader shader) + { + if (shader == null) + { + return true; + } + + if (shader == Shader.Find("Hidden/InternalErrorShader")) + { + return true; + } + + string shaderName = shader.name ?? string.Empty; + return shaderName.IndexOf("InternalErrorShader", StringComparison.OrdinalIgnoreCase) >= 0; + } + + private static bool IsPipelineMismatch(string shaderName, PipelineKind activePipeline) + { + if (string.IsNullOrEmpty(shaderName)) + { + return true; + } + + string lowerName = shaderName.ToLowerInvariant(); + bool shaderLooksUrp = lowerName.Contains("universal render pipeline") || lowerName.Contains("urp/"); + bool shaderLooksHdrp = lowerName.Contains("high definition render pipeline") || lowerName.Contains("hdrp/"); + bool shaderLooksSrp = shaderLooksUrp || shaderLooksHdrp; + bool shaderLooksBuiltin = LooksLikeBuiltInShader(lowerName, shaderLooksSrp); + + return activePipeline switch + { + PipelineKind.HighDefinition => shaderLooksUrp || (shaderLooksBuiltin && !shaderLooksHdrp), + PipelineKind.Universal => shaderLooksHdrp || (shaderLooksBuiltin && !shaderLooksUrp), + PipelineKind.BuiltIn => shaderLooksSrp, + _ => false, + }; + } + internal static Material GetOrCreateDefaultVFXMaterial(VFXComponentType componentType) { var pipeline = GetActivePipeline(); @@ -227,7 +304,7 @@ internal static Material GetOrCreateDefaultVFXMaterial(VFXComponentType componen if (material == null) { - Shader shader = ResolveDefaultUnlitShader(pipeline); + Shader shader = ResolveDefaultVFXShader(pipeline, componentType); if (shader == null) { shader = Shader.Find("Unlit/Color"); @@ -280,5 +357,89 @@ internal static Material GetOrCreateDefaultVFXMaterial(VFXComponentType componen return material; } + + private static Shader ResolveDefaultVFXShader(PipelineKind pipeline, VFXComponentType componentType) + { + if (componentType == VFXComponentType.ParticleSystem) + { + return pipeline switch + { + PipelineKind.Universal => TryFindShader(UrpParticleShaders) ?? ResolveDefaultUnlitShader(pipeline), + PipelineKind.HighDefinition => TryFindShader(HdrpUnlitShaders) ?? ResolveDefaultUnlitShader(pipeline), + PipelineKind.BuiltIn => TryFindShader(BuiltInParticleShaders) ?? ResolveDefaultUnlitShader(pipeline), + PipelineKind.Custom => TryFindShader(UrpParticleShaders) + ?? TryFindShader(BuiltInParticleShaders) + ?? TryFindShader(HdrpUnlitShaders) + ?? ResolveDefaultUnlitShader(pipeline), + _ => ResolveDefaultUnlitShader(pipeline), + }; + } + + return ResolveDefaultUnlitShader(pipeline); + } + + private static bool LooksLikeBuiltInShader(string lowerName, bool shaderLooksSrp) + { + if (string.IsNullOrEmpty(lowerName)) + { + return false; + } + + if (lowerName == "standard" || + lowerName.StartsWith("legacy shaders/", StringComparison.Ordinal) || + lowerName.StartsWith("mobile/", StringComparison.Ordinal)) + { + return true; + } + + // Built-in non-SRP shader families commonly seen on particles/old content. + if (!shaderLooksSrp && + (lowerName.StartsWith("particles/", StringComparison.Ordinal) || + lowerName.StartsWith("unlit/", StringComparison.Ordinal))) + { + return true; + } + + return false; + } + + internal static Material GetOrCreateDefaultSceneMaterial() + { + var pipeline = GetActivePipeline(); + string cacheKey = $"{pipeline}_scene"; + if (s_DefaultSceneMaterials.TryGetValue(cacheKey, out Material cached) && cached != null) + { + return cached; + } + + Material material = null; + Shader shader = ResolveDefaultLitShader(pipeline) ?? ResolveDefaultUnlitShader(pipeline); + if (shader == null) + { + shader = Shader.Find("Unlit/Color"); + } + + if (shader != null) + { + material = new Material(shader); + material.name = $"Auto_Default_Scene_{pipeline}"; + if (material.HasProperty("_Color")) + { + material.SetColor("_Color", Color.white); + } + if (material.HasProperty("_BaseColor")) + { + material.SetColor("_BaseColor", Color.white); + } + McpLog.Info($"[RenderPipelineUtility] Created default scene material using {shader.name}"); + } + + if (material != null) + { + s_DefaultSceneMaterials[cacheKey] = material; + } + + return material; + } } } diff --git a/MCPForUnity/Editor/Helpers/RendererHelpers.cs b/MCPForUnity/Editor/Helpers/RendererHelpers.cs index 9025e5325..de7f48da4 100644 --- a/MCPForUnity/Editor/Helpers/RendererHelpers.cs +++ b/MCPForUnity/Editor/Helpers/RendererHelpers.cs @@ -12,54 +12,59 @@ namespace MCPForUnity.Editor.Helpers /// public static class RendererHelpers { + public readonly struct EnsureMaterialResult + { + public EnsureMaterialResult(bool materialReplaced, string replacementReason) + { + MaterialReplaced = materialReplaced; + ReplacementReason = replacementReason ?? string.Empty; + } + + public bool MaterialReplaced { get; } + public string ReplacementReason { get; } + } + /// /// Ensures a renderer has a material assigned. If not, auto-assigns a default material /// based on the render pipeline and component type. /// /// The renderer to check - public static void EnsureMaterial(Renderer renderer) + public static EnsureMaterialResult EnsureMaterial(Renderer renderer) { if (renderer == null) { - return; + return new EnsureMaterialResult(false, "renderer_missing"); } var existingMaterial = renderer.sharedMaterial; - if (IsUsableMaterial(existingMaterial)) + string replacementReason = string.Empty; + bool pipelineInvalid = RenderPipelineUtility.IsMaterialInvalidForActivePipeline(existingMaterial, out string pipelineReason); + if (existingMaterial != null && !pipelineInvalid && IsUsableMaterial(existingMaterial)) { - return; + return new EnsureMaterialResult(false, string.Empty); } if (existingMaterial != null) { var shaderName = existingMaterial.shader != null ? existingMaterial.shader.name : "(null)"; McpLog.Warn($"[RendererHelpers] Replacing invalid VFX material '{existingMaterial.name}' (shader: {shaderName})."); + replacementReason = !string.IsNullOrWhiteSpace(pipelineReason) ? pipelineReason : "invalid_material"; } - - RenderPipelineUtility.VFXComponentType? componentType = null; - if (renderer is ParticleSystemRenderer) - { - componentType = RenderPipelineUtility.VFXComponentType.ParticleSystem; - } - else if (renderer is LineRenderer) + else { - componentType = RenderPipelineUtility.VFXComponentType.LineRenderer; - } - else if (renderer is TrailRenderer) - { - componentType = RenderPipelineUtility.VFXComponentType.TrailRenderer; + replacementReason = "missing_material"; } - if (componentType.HasValue) + Material replacement = ResolveReplacementMaterial(renderer); + if (replacement != null) { - Material defaultMat = RenderPipelineUtility.GetOrCreateDefaultVFXMaterial(componentType.Value); - if (defaultMat != null) - { - Undo.RecordObject(renderer, "Assign default VFX material"); - renderer.sharedMaterial = defaultMat; - EditorUtility.SetDirty(renderer); - } + Undo.RecordObject(renderer, "Assign default renderer material"); + renderer.sharedMaterial = replacement; + EditorUtility.SetDirty(renderer); + return new EnsureMaterialResult(true, replacementReason); } + + return new EnsureMaterialResult(false, replacementReason); } private static bool IsUsableMaterial(Material material) @@ -84,6 +89,23 @@ private static bool IsUsableMaterial(Material material) return shader.isSupported; } + private static Material ResolveReplacementMaterial(Renderer renderer) + { + if (renderer is ParticleSystemRenderer) + { + return RenderPipelineUtility.GetOrCreateDefaultVFXMaterial(RenderPipelineUtility.VFXComponentType.ParticleSystem); + } + if (renderer is LineRenderer) + { + return RenderPipelineUtility.GetOrCreateDefaultVFXMaterial(RenderPipelineUtility.VFXComponentType.LineRenderer); + } + if (renderer is TrailRenderer) + { + return RenderPipelineUtility.GetOrCreateDefaultVFXMaterial(RenderPipelineUtility.VFXComponentType.TrailRenderer); + } + return RenderPipelineUtility.GetOrCreateDefaultSceneMaterial(); + } + /// /// Applies common Renderer properties (shadows, lighting, probes, sorting, rendering layer). /// Used by ParticleSetRenderer, LineSetProperties, TrailSetProperties. @@ -272,4 +294,3 @@ public static void ApplyLineTrailProperties(JObject @params, List change } } - diff --git a/MCPForUnity/Editor/Resources/Project/ProjectInfo.cs b/MCPForUnity/Editor/Resources/Project/ProjectInfo.cs index 6e6d12f93..236acaba0 100644 --- a/MCPForUnity/Editor/Resources/Project/ProjectInfo.cs +++ b/MCPForUnity/Editor/Resources/Project/ProjectInfo.cs @@ -1,9 +1,11 @@ using System; using System.IO; +using System.Reflection; using MCPForUnity.Editor.Helpers; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; +using PackageInfo = UnityEditor.PackageManager.PackageInfo; namespace MCPForUnity.Editor.Resources.Project { @@ -27,7 +29,15 @@ public static object HandleCommand(JObject @params) projectName = projectName ?? "", unityVersion = Application.unityVersion, platform = EditorUserBuildSettings.activeBuildTarget.ToString(), - assetsPath = assetsPath + assetsPath = assetsPath, + renderPipeline = RenderPipelineUtility.GetActivePipeline().ToString(), + activeInputHandler = GetActiveInputHandler(), + packages = new + { + ugui = IsPackageInstalled("com.unity.ugui"), + textmeshpro = IsPackageInstalled("com.unity.textmeshpro"), + inputsystem = IsPackageInstalled("com.unity.inputsystem"), + } }; return new SuccessResponse("Retrieved project info.", info); @@ -37,5 +47,48 @@ public static object HandleCommand(JObject @params) return new ErrorResponse($"Error getting project info: {e.Message}"); } } + + /// + /// Reads PlayerSettings.activeInputHandler via reflection to avoid + /// compile-time dependency on the Input System package. + /// Returns "Old" (0), "New" (1), or "Both" (2). + /// + private static string GetActiveInputHandler() + { + try + { + var prop = typeof(PlayerSettings).GetProperty( + "activeInputHandler", + BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + if (prop == null) + return "Old"; + + int value = (int)prop.GetValue(null); + return value switch + { + 0 => "Old", + 1 => "New", + 2 => "Both", + _ => "Old" + }; + } + catch + { + return "Old"; + } + } + + private static bool IsPackageInstalled(string packageName) + { + try + { + return PackageInfo.FindForAssetPath("Packages/" + packageName) != null; + } + catch + { + return false; + } + } } } diff --git a/MCPForUnity/Editor/Tools/GameObjects/GameObjectLookAt.cs b/MCPForUnity/Editor/Tools/GameObjects/GameObjectLookAt.cs new file mode 100644 index 000000000..bfeb9c13d --- /dev/null +++ b/MCPForUnity/Editor/Tools/GameObjects/GameObjectLookAt.cs @@ -0,0 +1,63 @@ +#nullable disable +using MCPForUnity.Editor.Helpers; +using Newtonsoft.Json.Linq; +using UnityEditor; +using UnityEngine; + +namespace MCPForUnity.Editor.Tools.GameObjects +{ + internal static class GameObjectLookAt + { + /// + /// Rotates a GameObject to face a world position or another GameObject. + /// Parameters: + /// target - The GO to rotate (name/path/instanceID) + /// look_at_target - World position [x,y,z] or GO reference (name/path/instanceID) to look at + /// look_at_up - Optional up vector [x,y,z], defaults to Vector3.up + /// + internal static object Handle(JObject @params, JToken targetToken, string searchMethod) + { + GameObject targetGo = ManageGameObjectCommon.FindObjectInternal(targetToken, searchMethod); + if (targetGo == null) + { + return new ErrorResponse($"Target GameObject ('{targetToken}') not found using method '{searchMethod ?? "default"}'."); + } + + JToken lookAtToken = @params["look_at_target"] ?? @params["lookAtTarget"]; + if (lookAtToken == null) + { + return new ErrorResponse("'look_at_target' parameter is required for 'look_at' action. Provide a world position [x,y,z] or a GameObject name/path/ID."); + } + + // Try parsing as a position vector first + Vector3? lookAtPos = VectorParsing.ParseVector3(lookAtToken); + if (!lookAtPos.HasValue) + { + // Not a vector — treat as a GO reference, using the same search method as for the main target + GameObject lookAtGo = ManageGameObjectCommon.FindObjectInternal(lookAtToken, searchMethod); + if (lookAtGo == null) + { + return new ErrorResponse($"look_at_target '{lookAtToken}' could not be resolved as a position [x,y,z] or found as a GameObject."); + } + lookAtPos = lookAtGo.transform.position; + } + + Vector3 upVector = VectorParsing.ParseVector3OrDefault(@params["look_at_up"] ?? @params["lookAtUp"], Vector3.up); + + Undo.RecordObject(targetGo.transform, $"LookAt {targetGo.name}"); + targetGo.transform.LookAt(lookAtPos.Value, upVector); + + var euler = targetGo.transform.rotation.eulerAngles; + return new SuccessResponse( + $"'{targetGo.name}' now looking at ({lookAtPos.Value.x:F2}, {lookAtPos.Value.y:F2}, {lookAtPos.Value.z:F2}).", + new + { + name = targetGo.name, + instanceID = targetGo.GetInstanceID(), + rotation = new[] { euler.x, euler.y, euler.z }, + lookAtPosition = new[] { lookAtPos.Value.x, lookAtPos.Value.y, lookAtPos.Value.z }, + } + ); + } + } +} diff --git a/MCPForUnity/Editor/Tools/GameObjects/GameObjectLookAt.cs.meta b/MCPForUnity/Editor/Tools/GameObjects/GameObjectLookAt.cs.meta new file mode 100644 index 000000000..07cb7cc9f --- /dev/null +++ b/MCPForUnity/Editor/Tools/GameObjects/GameObjectLookAt.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fede847680da4b11a1c9e01d98ffbf16 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs index 6900234e9..d7a1b47bb 100644 --- a/MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs +++ b/MCPForUnity/Editor/Tools/GameObjects/ManageGameObject.cs @@ -100,6 +100,8 @@ public static object HandleCommand(JObject @params) return GameObjectDuplicate.Handle(@params, targetToken, searchMethod); case "move_relative": return GameObjectMoveRelative.Handle(@params, targetToken, searchMethod); + case "look_at": + return GameObjectLookAt.Handle(@params, targetToken, searchMethod); default: return new ErrorResponse($"Unknown action: '{action}'."); diff --git a/MCPForUnity/Editor/Tools/ManageMaterial.cs b/MCPForUnity/Editor/Tools/ManageMaterial.cs index e6d7cd22e..14741f4d2 100644 --- a/MCPForUnity/Editor/Tools/ManageMaterial.cs +++ b/MCPForUnity/Editor/Tools/ManageMaterial.cs @@ -244,17 +244,22 @@ private static object AssignMaterialToRenderer(JObject @params) private static object SetRendererColor(JObject @params) { - string target = @params["target"]?.ToString(); - string searchMethod = @params["searchMethod"]?.ToString(); - JToken colorToken = @params["color"]; - int slot = @params["slot"]?.ToObject() ?? 0; - string mode = @params["mode"]?.ToString() ?? "property_block"; + var p = new ToolParams(@params); - if (string.IsNullOrEmpty(target) || colorToken == null) + var targetResult = p.GetRequired("target"); + var targetError = targetResult.GetOrError(out string target); + if (targetError != null) return targetError; + + string searchMethod = p.Get("searchMethod"); + JToken colorToken = p.GetRaw("color"); + if (colorToken == null) { - return new ErrorResponse("target and color are required"); + return new ErrorResponse("'color' parameter is required."); } + int slot = p.GetInt("slot") ?? 0; + string mode = p.Get("mode", "property_block"); + Color color; try { @@ -280,6 +285,8 @@ private static object SetRendererColor(JObject @params) return new ErrorResponse($"GameObject {go.name} has no Renderer component"); } + RendererHelpers.EnsureMaterial(renderer); + if (mode == "property_block") { if (slot < 0 || slot >= renderer.sharedMaterials.Length) @@ -293,12 +300,26 @@ private static object SetRendererColor(JObject @params) if (renderer.sharedMaterials[slot] != null) { Material mat = renderer.sharedMaterials[slot]; - if (mat.HasProperty("_BaseColor")) block.SetColor("_BaseColor", color); - else if (mat.HasProperty("_Color")) block.SetColor("_Color", color); - else block.SetColor("_Color", color); + bool wroteAnyProperty = false; + if (mat.HasProperty("_BaseColor")) + { + block.SetColor("_BaseColor", color); + wroteAnyProperty = true; + } + if (mat.HasProperty("_Color")) + { + block.SetColor("_Color", color); + wroteAnyProperty = true; + } + if (!wroteAnyProperty) + { + block.SetColor("_BaseColor", color); + block.SetColor("_Color", color); + } } else { + block.SetColor("_BaseColor", color); block.SetColor("_Color", color); } @@ -316,8 +337,7 @@ private static object SetRendererColor(JObject @params) return new ErrorResponse($"No material in slot {slot}"); } Undo.RecordObject(mat, "Set Material Color"); - if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", color); - else mat.SetColor("_Color", color); + SetColorProperties(mat, color); EditorUtility.SetDirty(mat); return new SuccessResponse("Set shared material color"); } @@ -334,16 +354,93 @@ private static object SetRendererColor(JObject @params) } // Note: Undo cannot fully revert material instantiation Undo.RecordObject(mat, "Set Instance Material Color"); - if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", color); - else mat.SetColor("_Color", color); + SetColorProperties(mat, color); return new SuccessResponse("Set instance material color", new { warning = "Material instance created; Undo cannot fully revert instantiation." }); } return new ErrorResponse("Invalid slot"); } + else if (mode == "create_unique") + { + return CreateUniqueAndAssign(renderer, go, color, slot); + } return new ErrorResponse($"Unknown mode: {mode}"); } + private static void SetColorProperties(Material mat, Color color) + { + bool wrote = false; + if (mat.HasProperty("_BaseColor")) + { + mat.SetColor("_BaseColor", color); + wrote = true; + } + if (mat.HasProperty("_Color")) + { + mat.SetColor("_Color", color); + wrote = true; + } + if (!wrote) + { + mat.SetColor("_BaseColor", color); + mat.SetColor("_Color", color); + } + } + + private static object CreateUniqueAndAssign(Renderer renderer, GameObject go, Color color, int slot) + { + string safeName = go.name.Replace(" ", "_"); + string matPath = $"Assets/Materials/{safeName}_{go.GetInstanceID()}_mat.mat"; + matPath = AssetPathUtility.SanitizeAssetPath(matPath); + if (matPath == null) + { + return new ErrorResponse($"Invalid GameObject name '{go.name}' — cannot build a safe material path."); + } + + // Ensure the Materials directory exists + if (!AssetDatabase.IsValidFolder("Assets/Materials")) + { + AssetDatabase.CreateFolder("Assets", "Materials"); + } + + Material existing = AssetDatabase.LoadAssetAtPath(matPath); + if (existing != null) + { + // Material already exists (e.g. retry) — update its color and re-assign + Undo.RecordObject(existing, "Update unique material color"); + SetColorProperties(existing, color); + EditorUtility.SetDirty(existing); + } + else + { + Shader shader = RenderPipelineUtility.ResolveShader("Standard"); + if (shader == null) + { + return new ErrorResponse("Could not resolve a suitable shader for the active render pipeline."); + } + + existing = new Material(shader); + SetColorProperties(existing, color); + AssetDatabase.CreateAsset(existing, matPath); + } + + AssetDatabase.SaveAssets(); + + // Assign to renderer + Undo.RecordObject(renderer, "Assign unique material"); + Material[] sharedMats = renderer.sharedMaterials; + if (slot < 0 || slot >= sharedMats.Length) + { + return new ErrorResponse($"Slot {slot} out of bounds (count: {sharedMats.Length})"); + } + sharedMats[slot] = existing; + renderer.sharedMaterials = sharedMats; + EditorUtility.SetDirty(renderer); + + return new SuccessResponse($"Created unique material at {matPath} and assigned to {go.name}", + new { materialPath = matPath }); + } + private static object GetMaterialInfo(JObject @params) { string materialPath = NormalizePath(@params["materialPath"]?.ToString()); diff --git a/MCPForUnity/Editor/Tools/ManageScene.cs b/MCPForUnity/Editor/Tools/ManageScene.cs index 2ceecd9a9..b9f27384c 100644 --- a/MCPForUnity/Editor/Tools/ManageScene.cs +++ b/MCPForUnity/Editor/Tools/ManageScene.cs @@ -27,6 +27,18 @@ private sealed class SceneCommand public string fileName { get; set; } = string.Empty; public int? superSize { get; set; } + // screenshot: camera selection, inline image, batch, view positioning + public string camera { get; set; } + public bool? includeImage { get; set; } + public int? maxResolution { get; set; } + public string batch { get; set; } // "surround" for multi-angle batch capture + public JToken lookAt { get; set; } // GO reference or [x,y,z] to aim at before capture + public Vector3? viewPosition { get; set; } // camera position for view-based capture + public Vector3? viewRotation { get; set; } // euler rotation for view-based capture + + // scene_view_frame + public JToken sceneViewTarget { get; set; } + // get_hierarchy paging + safety (summary-first) public JToken parent { get; set; } public int? pageSize { get; set; } @@ -49,6 +61,18 @@ private static SceneCommand ToSceneCommand(JObject p) fileName = (p["fileName"] ?? p["filename"])?.ToString() ?? string.Empty, superSize = ParamCoercion.CoerceIntNullable(p["superSize"] ?? p["super_size"] ?? p["supersize"]), + // screenshot: camera selection, inline image, batch, view positioning + camera = (p["camera"])?.ToString(), + includeImage = ParamCoercion.CoerceBoolNullable(p["includeImage"] ?? p["include_image"]), + maxResolution = ParamCoercion.CoerceIntNullable(p["maxResolution"] ?? p["max_resolution"]), + batch = (p["batch"])?.ToString(), + lookAt = p["lookAt"] ?? p["look_at"], + viewPosition = VectorParsing.ParseVector3(p["viewPosition"] ?? p["view_position"]), + viewRotation = VectorParsing.ParseVector3(p["viewRotation"] ?? p["view_rotation"]), + + // scene_view_frame + sceneViewTarget = p["sceneViewTarget"] ?? p["scene_view_target"], + // get_hierarchy paging + safety parent = p["parent"], pageSize = ParamCoercion.CoerceIntNullable(p["pageSize"] ?? p["page_size"]), @@ -157,11 +181,12 @@ public static object HandleCommand(JObject @params) case "get_build_settings": return GetBuildSettingsScenes(); case "screenshot": - return CaptureScreenshot(cmd.fileName, cmd.superSize); - // Add cases for modifying build settings, additive loading, unloading etc. + return CaptureScreenshot(cmd); + case "scene_view_frame": + return FrameSceneView(cmd); default: return new ErrorResponse( - $"Unknown action: '{action}'. Valid actions: create, load, save, get_hierarchy, get_active, get_build_settings, screenshot." + $"Unknown action: '{action}'. Valid actions: create, load, save, get_hierarchy, get_active, get_build_settings, screenshot, scene_view_frame." ); } } @@ -173,7 +198,8 @@ public static object HandleCommand(JObject @params) /// public static object ExecuteScreenshot(string fileName = null, int? superSize = null) { - return CaptureScreenshot(fileName, superSize); + var cmd = new SceneCommand { fileName = fileName ?? string.Empty, superSize = superSize }; + return CaptureScreenshot(cmd); } private static object CreateScene(string fullPath, string relativePath) @@ -355,11 +381,29 @@ private static object SaveScene(string fullPath, string relativePath) } } - private static object CaptureScreenshot(string fileName, int? superSize) + private static object CaptureScreenshot(SceneCommand cmd) { try { - int resolvedSuperSize = (superSize.HasValue && superSize.Value > 0) ? superSize.Value : 1; + // Batch capture (e.g., "surround" for 6 angles around the scene) + if (!string.IsNullOrEmpty(cmd.batch)) + { + if (cmd.batch.Equals("surround", StringComparison.OrdinalIgnoreCase)) + return CaptureSurroundBatch(cmd); + return new ErrorResponse($"Unknown batch mode: '{cmd.batch}'. Valid modes: 'surround'."); + } + + // Positioned view-based capture (creates temp camera at view_position, aimed at look_at) + if ((cmd.lookAt != null && cmd.lookAt.Type != JTokenType.Null) || cmd.viewPosition.HasValue) + { + return CapturePositionedScreenshot(cmd); + } + + string fileName = cmd.fileName; + int resolvedSuperSize = (cmd.superSize.HasValue && cmd.superSize.Value > 0) ? cmd.superSize.Value : 1; + bool includeImage = cmd.includeImage ?? false; + int maxResolution = cmd.maxResolution ?? 0; // 0 = let ScreenshotUtility default to 640 + string cameraRef = cmd.camera; // Batch mode warning if (Application.isBatchMode) @@ -367,7 +411,62 @@ private static object CaptureScreenshot(string fileName, int? superSize) McpLog.Warn("[ManageScene] Screenshot capture in batch mode uses camera-based fallback. Results may vary."); } - // Check Screen Capture module availability and warn if not available + // Resolve camera target + Camera targetCamera = null; + if (!string.IsNullOrEmpty(cameraRef)) + { + targetCamera = ResolveCamera(cameraRef); + if (targetCamera == null) + { + return new ErrorResponse($"Camera '{cameraRef}' not found. Provide a Camera GameObject name, path, or instance ID."); + } + } + + // When a specific camera is requested or include_image is true, always use camera-based capture + // (synchronous, gives us bytes in memory for base64). + if (targetCamera != null || includeImage) + { + if (targetCamera == null) + { + targetCamera = Camera.main; + if (targetCamera == null) + { + var allCams = UnityEngine.Object.FindObjectsOfType(); + targetCamera = allCams.Length > 0 ? allCams[0] : null; + } + } + if (targetCamera == null) + { + return new ErrorResponse("No camera found in the scene. Add a Camera to use screenshot with camera or include_image."); + } + + if (!Application.isBatchMode) EnsureGameView(); + + ScreenshotCaptureResult result = ScreenshotUtility.CaptureFromCameraToAssetsFolder( + targetCamera, fileName, resolvedSuperSize, ensureUniqueFileName: true, + includeImage: includeImage, maxResolution: maxResolution); + + AssetDatabase.ImportAsset(result.AssetsRelativePath, ImportAssetOptions.ForceSynchronousImport); + string message = $"Screenshot captured to '{result.AssetsRelativePath}' (camera: {targetCamera.name})."; + + var data = new Dictionary + { + { "path", result.AssetsRelativePath }, + { "fullPath", result.FullPath }, + { "superSize", result.SuperSize }, + { "isAsync", false }, + { "camera", targetCamera.name }, + }; + if (includeImage && result.ImageBase64 != null) + { + data["imageBase64"] = result.ImageBase64; + data["imageWidth"] = result.ImageWidth; + data["imageHeight"] = result.ImageHeight; + } + return new SuccessResponse(message, data); + } + + // Default path: use ScreenCapture API if available, camera fallback otherwise bool screenCaptureAvailable = ScreenshotUtility.IsScreenCaptureModuleAvailable; bool hasCameraFallback = Camera.main != null || UnityEngine.Object.FindObjectsOfType().Length > 0; @@ -380,57 +479,366 @@ private static object CaptureScreenshot(string fileName, int? superSize) "or (2) Add a Camera to your scene for camera-based fallback capture." ); } - if (!screenCaptureAvailable) { - McpLog.Warn("[ManageScene] Screen Capture module not enabled. Using camera-based fallback. " + - "For best results, enable it: Window > Package Manager > Built-in > Screen Capture > Enable."); + McpLog.Warn("[ManageScene] Screen Capture module not enabled. Using camera-based fallback."); } #else if (!hasCameraFallback) { return new ErrorResponse( - "No camera found in the scene. Screenshot capture on Unity versions before 2022.1 requires a Camera in the scene. " + - "Please add a Camera to your scene or upgrade to Unity 2022.1+ for ScreenCapture API support." + "No camera found in the scene. Screenshot capture on Unity versions before 2022.1 requires a Camera in the scene." ); } #endif - // Best-effort: ensure Game View exists and repaints before capture. - if (!Application.isBatchMode) - { - EnsureGameView(); - } + if (!Application.isBatchMode) EnsureGameView(); - ScreenshotCaptureResult result = ScreenshotUtility.CaptureToAssetsFolder(fileName, resolvedSuperSize, ensureUniqueFileName: true); + ScreenshotCaptureResult defaultResult = ScreenshotUtility.CaptureToAssetsFolder(fileName, resolvedSuperSize, ensureUniqueFileName: true); - // ScreenCapture.CaptureScreenshot is async. Import after the file actually hits disk. - if (result.IsAsync) + if (defaultResult.IsAsync) + ScheduleAssetImportWhenFileExists(defaultResult.AssetsRelativePath, defaultResult.FullPath, timeoutSeconds: 30.0); + else + AssetDatabase.ImportAsset(defaultResult.AssetsRelativePath, ImportAssetOptions.ForceSynchronousImport); + + string verb = defaultResult.IsAsync ? "Screenshot requested" : "Screenshot captured"; + return new SuccessResponse( + $"{verb} to '{defaultResult.AssetsRelativePath}'.", + new + { + path = defaultResult.AssetsRelativePath, + fullPath = defaultResult.FullPath, + superSize = defaultResult.SuperSize, + isAsync = defaultResult.IsAsync, + } + ); + } + catch (Exception e) + { + return new ErrorResponse($"Error capturing screenshot: {e.Message}"); + } + } + + /// + /// Captures screenshots from 6 angles around scene bounds (or a look_at target) for AI scene understanding. + /// Does NOT save to disk — returns all images as inline base64 PNGs. Always uses camera-based capture. + /// + private static object CaptureSurroundBatch(SceneCommand cmd) + { + try + { + int maxRes = cmd.maxResolution ?? 480; + + Vector3 center; + float radius; + + // If look_at is provided, center on that target instead of scene bounds + if (cmd.lookAt != null && cmd.lookAt.Type != JTokenType.Null) { - ScheduleAssetImportWhenFileExists(result.AssetsRelativePath, result.FullPath, timeoutSeconds: 30.0); + var lookAtPos = VectorParsing.ParseVector3(cmd.lookAt); + if (lookAtPos.HasValue) + { + center = lookAtPos.Value; + radius = 5f; + } + else + { + Scene lookAtScene = EditorSceneManager.GetActiveScene(); + var lookAtGo = ResolveGameObject(cmd.lookAt, lookAtScene); + if (lookAtGo == null) + return new ErrorResponse($"look_at target '{cmd.lookAt}' not found for batch capture."); + + Bounds targetBounds = new Bounds(lookAtGo.transform.position, Vector3.zero); + foreach (var r in lookAtGo.GetComponentsInChildren()) + { + if (r != null && r.gameObject.activeInHierarchy) targetBounds.Encapsulate(r.bounds); + } + center = targetBounds.center; + radius = targetBounds.extents.magnitude * 1.8f; + radius = Mathf.Max(radius, 3f); + } } else { - AssetDatabase.ImportAsset(result.AssetsRelativePath, ImportAssetOptions.ForceSynchronousImport); + // Default: calculate combined bounds of all renderers in the scene + Bounds bounds = new Bounds(Vector3.zero, Vector3.zero); + bool hasBounds = false; + var renderers = UnityEngine.Object.FindObjectsOfType(); + foreach (var r in renderers) + { + if (r == null || !r.gameObject.activeInHierarchy) continue; + if (!hasBounds) + { + bounds = r.bounds; + hasBounds = true; + } + else + { + bounds.Encapsulate(r.bounds); + } + } + + if (!hasBounds) + return new ErrorResponse("No renderers found in the scene. Cannot determine scene bounds for batch capture."); + + center = bounds.center; + radius = bounds.extents.magnitude * 1.8f; + radius = Mathf.Max(radius, 3f); } - string verb = result.IsAsync ? "Screenshot requested" : "Screenshot captured"; - string message = $"{verb} to '{result.AssetsRelativePath}' (full: {result.FullPath})."; + // Define 6 viewpoints: front, back, left, right, top, bird's-eye (45° elevated front-right) + var angles = new[] + { + ("front", new Vector3(center.x, center.y, center.z - radius)), + ("back", new Vector3(center.x, center.y, center.z + radius)), + ("left", new Vector3(center.x - radius, center.y, center.z)), + ("right", new Vector3(center.x + radius, center.y, center.z)), + ("top", new Vector3(center.x, center.y + radius, center.z)), + ("bird_eye", new Vector3(center.x + radius * 0.7f, center.y + radius * 0.7f, center.z - radius * 0.7f)), + }; + + // Create a temporary camera + var tempGo = new GameObject("__MCP_MultiAngle_Temp_Camera__"); + Camera tempCam = tempGo.AddComponent(); + tempCam.fieldOfView = 60f; + tempCam.nearClipPlane = 0.1f; + tempCam.farClipPlane = radius * 4f; + tempCam.clearFlags = CameraClearFlags.Skybox; + + var screenshots = new List(); + try + { + foreach (var (label, pos) in angles) + { + tempCam.transform.position = pos; + tempCam.transform.LookAt(center); + + var (b64, w, h) = ScreenshotUtility.RenderCameraToBase64(tempCam, maxRes); + screenshots.Add(new Dictionary + { + { "angle", label }, + { "position", new[] { pos.x, pos.y, pos.z } }, + { "imageBase64", b64 }, + { "imageWidth", w }, + { "imageHeight", h }, + }); + } + } + finally + { + UnityEngine.Object.DestroyImmediate(tempGo); + } return new SuccessResponse( - message, + $"Captured {screenshots.Count} multi-angle screenshots (max {maxRes}px). Scene bounds center: ({center.x:F1}, {center.y:F1}, {center.z:F1}), radius: {radius:F1}.", new { - path = result.AssetsRelativePath, - fullPath = result.FullPath, - superSize = result.SuperSize, - isAsync = result.IsAsync, + sceneCenter = new[] { center.x, center.y, center.z }, + sceneRadius = radius, + screenshots = screenshots, } ); } catch (Exception e) { - return new ErrorResponse($"Error capturing screenshot: {e.Message}"); + return new ErrorResponse($"Error capturing batch screenshots: {e.Message}"); + } + } + + /// + /// Captures a single screenshot from a temporary camera placed at view_position and aimed at look_at. + /// Returns inline base64 PNG (no file saved to disk). + /// + private static object CapturePositionedScreenshot(SceneCommand cmd) + { + try + { + int maxRes = cmd.maxResolution ?? 640; + + // Resolve where to aim + Vector3? targetPos = null; + if (cmd.lookAt != null && cmd.lookAt.Type != JTokenType.Null) + { + var parsedPos = VectorParsing.ParseVector3(cmd.lookAt); + if (parsedPos.HasValue) + { + targetPos = parsedPos.Value; + } + else + { + Scene activeScene = EditorSceneManager.GetActiveScene(); + var lookAtGo = ResolveGameObject(cmd.lookAt, activeScene); + if (lookAtGo == null) + return new ErrorResponse($"look_at target '{cmd.lookAt}' not found."); + targetPos = lookAtGo.transform.position; + } + } + + // Determine camera position + Vector3 camPos; + if (cmd.viewPosition.HasValue) + { + camPos = cmd.viewPosition.Value; + } + else if (targetPos.HasValue) + { + // Default: offset from look_at target + camPos = targetPos.Value + new Vector3(0, 2, -5); + } + else + { + return new ErrorResponse("Provide 'look_at' or 'view_position' for a positioned screenshot."); + } + + // Create temporary camera + var tempGo = new GameObject("__MCP_PositionedCapture_Temp__"); + Camera tempCam = tempGo.AddComponent(); + tempCam.fieldOfView = 60f; + tempCam.nearClipPlane = 0.1f; + tempCam.farClipPlane = 1000f; + tempCam.clearFlags = CameraClearFlags.Skybox; + tempCam.transform.position = camPos; + + try + { + if (cmd.viewRotation.HasValue) + tempCam.transform.rotation = Quaternion.Euler(cmd.viewRotation.Value); + else if (targetPos.HasValue) + tempCam.transform.LookAt(targetPos.Value); + + var (b64, w, h) = ScreenshotUtility.RenderCameraToBase64(tempCam, maxRes); + + var data = new Dictionary + { + { "imageBase64", b64 }, + { "imageWidth", w }, + { "imageHeight", h }, + { "viewPosition", new[] { camPos.x, camPos.y, camPos.z } }, + }; + if (targetPos.HasValue) + data["lookAt"] = new[] { targetPos.Value.x, targetPos.Value.y, targetPos.Value.z }; + + return new SuccessResponse( + $"Positioned screenshot captured (max {maxRes}px).", + data + ); + } + finally + { + UnityEngine.Object.DestroyImmediate(tempGo); + } + } + catch (Exception e) + { + return new ErrorResponse($"Error capturing positioned screenshot: {e.Message}"); + } + } + + /// + /// Resolves a camera by name, path, or instance ID. + /// + private static Camera ResolveCamera(string cameraRef) + { + if (string.IsNullOrEmpty(cameraRef)) return null; + + // Try instance ID + if (int.TryParse(cameraRef, out int id)) + { + var obj = EditorUtility.InstanceIDToObject(id); + if (obj is Camera cam) return cam; + if (obj is GameObject go) return go.GetComponent(); + } + + // Search all cameras by name or path + var allCams = UnityEngine.Object.FindObjectsOfType(); + foreach (var cam in allCams) + { + if (cam.name == cameraRef) return cam; + if (cam.gameObject.name == cameraRef) return cam; + } + + // Try path-based lookup + if (cameraRef.Contains("/")) + { + var ids = GameObjectLookup.SearchGameObjects("by_path", cameraRef, includeInactive: false, maxResults: 1); + if (ids.Count > 0) + { + var go = GameObjectLookup.FindById(ids[0]); + if (go != null) return go.GetComponent(); + } + } + + return null; + } + + /// + /// Frames the Scene View on a target GameObject or the entire scene. + /// + private static object FrameSceneView(SceneCommand cmd) + { + try + { + var sceneView = SceneView.lastActiveSceneView; + if (sceneView == null) + { + return new ErrorResponse("No active Scene View found. Open a Scene View window first."); + } + + if (cmd.sceneViewTarget != null && cmd.sceneViewTarget.Type != JTokenType.Null) + { + Scene activeScene = EditorSceneManager.GetActiveScene(); + GameObject target = ResolveGameObject(cmd.sceneViewTarget, activeScene); + if (target == null) + { + return new ErrorResponse($"Target GameObject '{cmd.sceneViewTarget}' not found for scene_view_frame."); + } + + // Calculate bounds from renderers, colliders, or transform + Bounds bounds = new Bounds(target.transform.position, Vector3.zero); + var renderers = target.GetComponentsInChildren(); + if (renderers.Length > 0) + { + bounds = renderers[0].bounds; + for (int i = 1; i < renderers.Length; i++) + bounds.Encapsulate(renderers[i].bounds); + } + else + { + var colliders = target.GetComponentsInChildren(); + if (colliders.Length > 0) + { + bounds = colliders[0].bounds; + for (int i = 1; i < colliders.Length; i++) + bounds.Encapsulate(colliders[i].bounds); + } + else + { + bounds = new Bounds(target.transform.position, Vector3.one); + } + } + + sceneView.Frame(bounds, false); + return new SuccessResponse($"Scene View framed on '{target.name}'.", new { target = target.name }); + } + else + { + // Frame entire scene by computing combined bounds of all renderers + Bounds allBounds = new Bounds(Vector3.zero, Vector3.zero); + bool hasAny = false; + foreach (var r in UnityEngine.Object.FindObjectsOfType()) + { + if (r == null || !r.gameObject.activeInHierarchy) continue; + if (!hasAny) { allBounds = r.bounds; hasAny = true; } + else allBounds.Encapsulate(r.bounds); + } + if (!hasAny) allBounds = new Bounds(Vector3.zero, Vector3.one * 10f); + sceneView.Frame(allBounds, false); + return new SuccessResponse("Scene View framed on entire scene."); + } + } + catch (Exception e) + { + return new ErrorResponse($"Error framing Scene View: {e.Message}"); } } diff --git a/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs b/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs index b6955cffc..9113b1a50 100644 --- a/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs +++ b/MCPForUnity/Editor/Tools/Vfx/ParticleControl.cs @@ -132,13 +132,17 @@ public static object Control(JObject @params, string action) ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); if (ps == null) return new { success = false, message = "ParticleSystem not found" }; + RendererHelpers.EnsureMaterialResult ensureResult = default; + bool materialChecked = false; + // Ensure material is assigned before playing if (action == "play" || action == "restart") { var renderer = ps.GetComponent(); if (renderer != null) { - RendererHelpers.EnsureMaterial(renderer); + ensureResult = RendererHelpers.EnsureMaterial(renderer); + materialChecked = true; } } @@ -154,7 +158,13 @@ public static object Control(JObject @params, string action) default: return new { success = false, message = $"Unknown action: {action}" }; } - return new { success = true, message = $"ParticleSystem {action}" }; + return new + { + success = true, + message = $"ParticleSystem {action}", + materialReplaced = materialChecked ? ensureResult.MaterialReplaced : false, + replacementReason = materialChecked ? ensureResult.ReplacementReason : string.Empty, + }; } public static object AddBurst(JObject @params) @@ -164,9 +174,12 @@ public static object AddBurst(JObject @params) // Ensure material is assigned var renderer = ps.GetComponent(); + RendererHelpers.EnsureMaterialResult ensureResult = default; + bool materialChecked = false; if (renderer != null) { - RendererHelpers.EnsureMaterial(renderer); + ensureResult = RendererHelpers.EnsureMaterial(renderer); + materialChecked = true; } Undo.RecordObject(ps, "Add Burst"); @@ -190,7 +203,14 @@ public static object AddBurst(JObject @params) emission.SetBursts(bursts); EditorUtility.SetDirty(ps); - return new { success = true, message = $"Added burst at t={time}", burstIndex = idx }; + return new + { + success = true, + message = $"Added burst at t={time}", + burstIndex = idx, + materialReplaced = materialChecked ? ensureResult.MaterialReplaced : false, + replacementReason = materialChecked ? ensureResult.ReplacementReason : string.Empty, + }; } public static object ClearBursts(JObject @params) diff --git a/MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs b/MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs index 21c0384fa..bc66cc295 100644 --- a/MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs +++ b/MCPForUnity/Editor/Tools/Vfx/ParticleWrite.cs @@ -9,17 +9,28 @@ namespace MCPForUnity.Editor.Tools.Vfx { internal static class ParticleWrite { - public static object SetMain(JObject @params) + private static ParticleSystemRenderer EnsureParticleRendererMaterial(ParticleSystem ps) { - ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); - if (ps == null) return new { success = false, message = "ParticleSystem not found" }; + if (ps == null) + { + return null; + } - // Ensure material is assigned before any configuration var renderer = ps.GetComponent(); if (renderer != null) { RendererHelpers.EnsureMaterial(renderer); } + return renderer; + } + + public static object SetMain(JObject @params) + { + ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); + if (ps == null) return new { success = false, message = "ParticleSystem not found" }; + + // Ensure material is assigned before any configuration. + EnsureParticleRendererMaterial(ps); // Stop particle system if it's playing and duration needs to be changed bool wasPlaying = ps.isPlaying; @@ -65,12 +76,8 @@ public static object SetEmission(JObject @params) ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); if (ps == null) return new { success = false, message = "ParticleSystem not found" }; - // Ensure material is assigned - var renderer = ps.GetComponent(); - if (renderer != null) - { - RendererHelpers.EnsureMaterial(renderer); - } + // Ensure material is assigned. + EnsureParticleRendererMaterial(ps); Undo.RecordObject(ps, "Set ParticleSystem Emission"); var emission = ps.emission; @@ -89,12 +96,8 @@ public static object SetShape(JObject @params) ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); if (ps == null) return new { success = false, message = "ParticleSystem not found" }; - // Ensure material is assigned - var renderer = ps.GetComponent(); - if (renderer != null) - { - RendererHelpers.EnsureMaterial(renderer); - } + // Ensure material is assigned. + EnsureParticleRendererMaterial(ps); Undo.RecordObject(ps, "Set ParticleSystem Shape"); var shape = ps.shape; @@ -119,12 +122,8 @@ public static object SetColorOverLifetime(JObject @params) ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); if (ps == null) return new { success = false, message = "ParticleSystem not found" }; - // Ensure material is assigned - var renderer = ps.GetComponent(); - if (renderer != null) - { - RendererHelpers.EnsureMaterial(renderer); - } + // Ensure material is assigned. + EnsureParticleRendererMaterial(ps); Undo.RecordObject(ps, "Set ParticleSystem Color Over Lifetime"); var col = ps.colorOverLifetime; @@ -142,12 +141,8 @@ public static object SetSizeOverLifetime(JObject @params) ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); if (ps == null) return new { success = false, message = "ParticleSystem not found" }; - // Ensure material is assigned - var renderer = ps.GetComponent(); - if (renderer != null) - { - RendererHelpers.EnsureMaterial(renderer); - } + // Ensure material is assigned. + EnsureParticleRendererMaterial(ps); Undo.RecordObject(ps, "Set ParticleSystem Size Over Lifetime"); var sol = ps.sizeOverLifetime; @@ -181,12 +176,8 @@ public static object SetVelocityOverLifetime(JObject @params) ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); if (ps == null) return new { success = false, message = "ParticleSystem not found" }; - // Ensure material is assigned - var renderer = ps.GetComponent(); - if (renderer != null) - { - RendererHelpers.EnsureMaterial(renderer); - } + // Ensure material is assigned. + EnsureParticleRendererMaterial(ps); Undo.RecordObject(ps, "Set ParticleSystem Velocity Over Lifetime"); var vol = ps.velocityOverLifetime; @@ -208,12 +199,8 @@ public static object SetNoise(JObject @params) ParticleSystem ps = ParticleCommon.FindParticleSystem(@params); if (ps == null) return new { success = false, message = "ParticleSystem not found" }; - // Ensure material is assigned - var renderer = ps.GetComponent(); - if (renderer != null) - { - RendererHelpers.EnsureMaterial(renderer); - } + // Ensure material is assigned. + EnsureParticleRendererMaterial(ps); Undo.RecordObject(ps, "Set ParticleSystem Noise"); var noise = ps.noise; @@ -240,7 +227,7 @@ public static object SetRenderer(JObject @params) if (renderer == null) return new { success = false, message = "ParticleSystemRenderer not found" }; // Ensure material is set before any other operations - RendererHelpers.EnsureMaterial(renderer); + RendererHelpers.EnsureMaterialResult ensureResult = RendererHelpers.EnsureMaterial(renderer); Undo.RecordObject(renderer, "Set ParticleSystem Renderer"); var changes = new List(); @@ -288,8 +275,17 @@ public static object SetRenderer(JObject @params) if (mat != null) { renderer.trailMaterial = mat; changes.Add("trailMaterial"); } } + // Re-check after renderer/material edits to catch invalid pipeline shader assignments. + ensureResult = RendererHelpers.EnsureMaterial(renderer); + EditorUtility.SetDirty(renderer); - return new { success = true, message = $"Updated renderer: {string.Join(", ", changes)}" }; + return new + { + success = true, + message = $"Updated renderer: {string.Join(", ", changes)}", + materialReplaced = ensureResult.MaterialReplaced, + replacementReason = ensureResult.ReplacementReason, + }; } } } diff --git a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs index 3176f0c75..075427dde 100644 --- a/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs +++ b/MCPForUnity/Runtime/Helpers/ScreenshotUtility.cs @@ -9,22 +9,35 @@ namespace MCPForUnity.Runtime.Helpers public readonly struct ScreenshotCaptureResult { public ScreenshotCaptureResult(string fullPath, string assetsRelativePath, int superSize) - : this(fullPath, assetsRelativePath, superSize, isAsync: false) + : this(fullPath, assetsRelativePath, superSize, isAsync: false, imageBase64: null, imageWidth: 0, imageHeight: 0) { } public ScreenshotCaptureResult(string fullPath, string assetsRelativePath, int superSize, bool isAsync) + : this(fullPath, assetsRelativePath, superSize, isAsync, imageBase64: null, imageWidth: 0, imageHeight: 0) + { + } + + public ScreenshotCaptureResult(string fullPath, string assetsRelativePath, int superSize, bool isAsync, + string imageBase64, int imageWidth, int imageHeight) { FullPath = fullPath; AssetsRelativePath = assetsRelativePath; SuperSize = superSize; IsAsync = isAsync; + ImageBase64 = imageBase64; + ImageWidth = imageWidth; + ImageHeight = imageHeight; } public string FullPath { get; } public string AssetsRelativePath { get; } public int SuperSize { get; } public bool IsAsync { get; } + /// Base64-encoded PNG image data. Only populated when include_image is true. + public string ImageBase64 { get; } + public int ImageWidth { get; } + public int ImageHeight { get; } } public static class ScreenshotUtility @@ -127,8 +140,16 @@ private static ScreenshotCaptureResult CaptureWithCameraFallback(string fileName /// /// Captures a screenshot from a specific camera by rendering into a temporary RenderTexture (works in Edit Mode). + /// When is true, the result includes a base64-encoded PNG (optionally + /// downscaled so the longest edge is at most ). /// - public static ScreenshotCaptureResult CaptureFromCameraToAssetsFolder(Camera camera, string fileName = null, int superSize = 1, bool ensureUniqueFileName = true) + public static ScreenshotCaptureResult CaptureFromCameraToAssetsFolder( + Camera camera, + string fileName = null, + int superSize = 1, + bool ensureUniqueFileName = true, + bool includeImage = false, + int maxResolution = 0) { if (camera == null) { @@ -147,6 +168,9 @@ public static ScreenshotCaptureResult CaptureFromCameraToAssetsFolder(Camera cam RenderTexture prevActive = RenderTexture.active; var rt = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32); Texture2D tex = null; + Texture2D downscaled = null; + string imageBase64 = null; + int imgW = 0, imgH = 0; try { camera.targetTexture = rt; @@ -159,28 +183,140 @@ public static ScreenshotCaptureResult CaptureFromCameraToAssetsFolder(Camera cam byte[] png = tex.EncodeToPNG(); File.WriteAllBytes(result.FullPath, png); - } - finally - { - camera.targetTexture = prevRT; - RenderTexture.active = prevActive; - RenderTexture.ReleaseTemporary(rt); - if (tex != null) + + if (includeImage) { - if (Application.isPlaying) + int targetMax = maxResolution > 0 ? maxResolution : 640; + if (width > targetMax || height > targetMax) { - UnityEngine.Object.Destroy(tex); + downscaled = DownscaleTexture(tex, targetMax); + byte[] smallPng = downscaled.EncodeToPNG(); + imageBase64 = System.Convert.ToBase64String(smallPng); + imgW = downscaled.width; + imgH = downscaled.height; } else { - UnityEngine.Object.DestroyImmediate(tex); + imageBase64 = System.Convert.ToBase64String(png); + imgW = width; + imgH = height; } } } + finally + { + camera.targetTexture = prevRT; + RenderTexture.active = prevActive; + RenderTexture.ReleaseTemporary(rt); + DestroyTexture(tex); + DestroyTexture(downscaled); + } + if (includeImage && imageBase64 != null) + { + return new ScreenshotCaptureResult( + result.FullPath, result.AssetsRelativePath, result.SuperSize, false, + imageBase64, imgW, imgH); + } return result; } + /// + /// Renders a camera to a Texture2D without saving to disk. Used for multi-angle captures. + /// Returns the base64-encoded PNG, downscaled to fit within . + /// + public static (string base64, int width, int height) RenderCameraToBase64(Camera camera, int maxResolution = 640) + { + if (camera == null) throw new ArgumentNullException(nameof(camera)); + + int width = Mathf.Max(1, camera.pixelWidth > 0 ? camera.pixelWidth : Screen.width); + int height = Mathf.Max(1, camera.pixelHeight > 0 ? camera.pixelHeight : Screen.height); + + RenderTexture prevRT = camera.targetTexture; + RenderTexture prevActive = RenderTexture.active; + var rt = RenderTexture.GetTemporary(width, height, 24, RenderTextureFormat.ARGB32); + Texture2D tex = null; + Texture2D downscaled = null; + try + { + camera.targetTexture = rt; + camera.Render(); + + RenderTexture.active = rt; + tex = new Texture2D(width, height, TextureFormat.RGBA32, false); + tex.ReadPixels(new Rect(0, 0, width, height), 0, 0); + tex.Apply(); + + int targetMax = maxResolution > 0 ? maxResolution : 640; + if (width > targetMax || height > targetMax) + { + downscaled = DownscaleTexture(tex, targetMax); + string b64 = System.Convert.ToBase64String(downscaled.EncodeToPNG()); + return (b64, downscaled.width, downscaled.height); + } + else + { + string b64 = System.Convert.ToBase64String(tex.EncodeToPNG()); + return (b64, width, height); + } + } + finally + { + camera.targetTexture = prevRT; + RenderTexture.active = prevActive; + RenderTexture.ReleaseTemporary(rt); + DestroyTexture(tex); + DestroyTexture(downscaled); + } + } + + /// + /// Downscales a Texture2D so that its longest edge is at most pixels. + /// Uses bilinear filtering via a temporary RenderTexture blit. + /// Caller must destroy the returned Texture2D. + /// + public static Texture2D DownscaleTexture(Texture2D source, int maxEdge) + { + if (source == null) + throw new System.ArgumentNullException(nameof(source)); + if (maxEdge <= 0) + throw new System.ArgumentOutOfRangeException(nameof(maxEdge), maxEdge, "maxEdge must be > 0."); + + int srcW = source.width; + int srcH = source.height; + float scale = Mathf.Min((float)maxEdge / srcW, (float)maxEdge / srcH); + scale = Mathf.Min(scale, 1f); // never upscale + int dstW = Mathf.Max(1, Mathf.RoundToInt(srcW * scale)); + int dstH = Mathf.Max(1, Mathf.RoundToInt(srcH * scale)); + + RenderTexture prevActive = RenderTexture.active; + var rt = RenderTexture.GetTemporary(dstW, dstH, 0, RenderTextureFormat.ARGB32); + rt.filterMode = FilterMode.Bilinear; + try + { + Graphics.Blit(source, rt); + RenderTexture.active = rt; + var dst = new Texture2D(dstW, dstH, TextureFormat.RGBA32, false); + dst.ReadPixels(new Rect(0, 0, dstW, dstH), 0, 0); + dst.Apply(); + return dst; + } + finally + { + RenderTexture.active = prevActive; + RenderTexture.ReleaseTemporary(rt); + } + } + + private static void DestroyTexture(Texture2D tex) + { + if (tex == null) return; + if (Application.isPlaying) + UnityEngine.Object.Destroy(tex); + else + UnityEngine.Object.DestroyImmediate(tex); + } + private static ScreenshotCaptureResult PrepareCaptureResult(string fileName, int superSize, bool ensureUniqueFileName, bool isAsync) { int size = Mathf.Max(1, superSize); diff --git a/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs b/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs index 33afd8783..f7ecc9837 100644 --- a/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs +++ b/MCPForUnity/Runtime/Serialization/UnityTypeConverters.cs @@ -24,7 +24,11 @@ public override void WriteJson(JsonWriter writer, Vector3 value, JsonSerializer public override Vector3 ReadJson(JsonReader reader, Type objectType, Vector3 existingValue, bool hasExistingValue, JsonSerializer serializer) { - JObject jo = JObject.Load(reader); + JToken token = JToken.Load(reader); + if (token is JArray arr && arr.Count >= 3) + return new Vector3((float)arr[0], (float)arr[1], (float)arr[2]); + if (token is not JObject jo) + throw new JsonSerializationException($"Cannot deserialize Vector3 from {token.Type}: '{token}'"); return new Vector3( (float)jo["x"], (float)jo["y"], @@ -47,7 +51,11 @@ public override void WriteJson(JsonWriter writer, Vector2 value, JsonSerializer public override Vector2 ReadJson(JsonReader reader, Type objectType, Vector2 existingValue, bool hasExistingValue, JsonSerializer serializer) { - JObject jo = JObject.Load(reader); + JToken token = JToken.Load(reader); + if (token is JArray arr && arr.Count >= 2) + return new Vector2((float)arr[0], (float)arr[1]); + if (token is not JObject jo) + throw new JsonSerializationException($"Cannot deserialize Vector2 from {token.Type}: '{token}'"); return new Vector2( (float)jo["x"], (float)jo["y"] @@ -73,7 +81,11 @@ public override void WriteJson(JsonWriter writer, Quaternion value, JsonSerializ public override Quaternion ReadJson(JsonReader reader, Type objectType, Quaternion existingValue, bool hasExistingValue, JsonSerializer serializer) { - JObject jo = JObject.Load(reader); + JToken token = JToken.Load(reader); + if (token is JArray arr && arr.Count >= 4) + return new Quaternion((float)arr[0], (float)arr[1], (float)arr[2], (float)arr[3]); + if (token is not JObject jo) + throw new JsonSerializationException($"Cannot deserialize Quaternion from {token.Type}: '{token}'"); return new Quaternion( (float)jo["x"], (float)jo["y"], @@ -178,7 +190,11 @@ public override void WriteJson(JsonWriter writer, Vector4 value, JsonSerializer public override Vector4 ReadJson(JsonReader reader, Type objectType, Vector4 existingValue, bool hasExistingValue, JsonSerializer serializer) { - JObject jo = JObject.Load(reader); + JToken token = JToken.Load(reader); + if (token is JArray arr && arr.Count >= 4) + return new Vector4((float)arr[0], (float)arr[1], (float)arr[2], (float)arr[3]); + if (token is not JObject jo) + throw new JsonSerializationException($"Cannot deserialize Vector4 from {token.Type}: '{token}'"); return new Vector4( (float)jo["x"], (float)jo["y"], diff --git a/Server/src/cli/commands/batch.py b/Server/src/cli/commands/batch.py index d7cac55bb..f4be5ced5 100644 --- a/Server/src/cli/commands/batch.py +++ b/Server/src/cli/commands/batch.py @@ -57,8 +57,8 @@ def batch_run(file: str, parallel: bool, fail_fast: bool): print_error("JSON file must contain an array of commands") sys.exit(1) - if len(commands) > 25: - print_error(f"Maximum 25 commands per batch, got {len(commands)}") + if len(commands) > 40: + print_error(f"Maximum 40 commands per batch, got {len(commands)}") sys.exit(1) params: dict[str, Any] = {"commands": commands} @@ -105,8 +105,8 @@ def batch_inline(commands_json: str, parallel: bool, fail_fast: bool): commands = parse_json_list_or_exit(commands_json, "commands") - if len(commands) > 25: - print_error(f"Maximum 25 commands per batch, got {len(commands)}") + if len(commands) > 40: + print_error(f"Maximum 40 commands per batch, got {len(commands)}") sys.exit(1) params: dict[str, Any] = {"commands": commands} diff --git a/Server/src/cli/commands/scene.py b/Server/src/cli/commands/scene.py index 18469b083..765d4c317 100644 --- a/Server/src/cli/commands/scene.py +++ b/Server/src/cli/commands/scene.py @@ -207,8 +207,46 @@ def build_settings(): type=int, help="Supersize multiplier (1-4)." ) +@click.option( + "--camera", "-c", + default=None, + help="Camera to capture from (name, path, or instance ID). Defaults to Camera.main." +) +@click.option( + "--include-image", is_flag=True, + help="Return screenshot as inline base64 PNG in the response." +) +@click.option( + "--max-resolution", "-r", + default=None, + type=int, + help="Max resolution (longest edge) for inline image. Default 640." +) +@click.option( + "--batch", "-b", + default=None, + help="Batch capture mode: 'surround' for 6 angles around the scene." +) +@click.option( + "--look-at", + default=None, + help="Target to aim at (GO name/path/ID or 'x,y,z' position)." +) +@click.option( + "--view-position", + default=None, + help="Camera position as 'x,y,z'." +) +@click.option( + "--view-rotation", + default=None, + help="Camera euler rotation as 'x,y,z'." +) @handle_unity_errors -def screenshot(filename: Optional[str], supersize: int): +def screenshot(filename: Optional[str], supersize: int, camera: Optional[str], + include_image: bool, max_resolution: Optional[int], + batch: Optional[str], look_at: Optional[str], + view_position: Optional[str], view_rotation: Optional[str]): """Capture a screenshot of the scene. \b @@ -216,6 +254,11 @@ def screenshot(filename: Optional[str], supersize: int): unity-mcp scene screenshot unity-mcp scene screenshot --filename "level_preview" unity-mcp scene screenshot --supersize 2 + unity-mcp scene screenshot --camera "SecondCamera" --include-image + unity-mcp scene screenshot --include-image --max-resolution 512 + unity-mcp scene screenshot --batch surround --max-resolution 256 + unity-mcp scene screenshot --look-at "Player" --max-resolution 512 + unity-mcp scene screenshot --view-position "0,10,-10" --look-at "0,0,0" """ config = get_config() @@ -224,8 +267,40 @@ def screenshot(filename: Optional[str], supersize: int): params["fileName"] = filename if supersize > 1: params["superSize"] = supersize + if camera: + params["camera"] = camera + if include_image: + params["includeImage"] = True + if max_resolution: + params["maxResolution"] = max_resolution + if batch: + params["batch"] = batch + if look_at: + # Try parsing as x,y,z coordinates + parts = look_at.split(",") + if len(parts) == 3: + try: + params["lookAt"] = [float(p.strip()) for p in parts] + except ValueError: + params["lookAt"] = look_at + else: + params["lookAt"] = look_at + if view_position: + parts = view_position.split(",") + if len(parts) == 3: + params["viewPosition"] = [float(p.strip()) for p in parts] + if view_rotation: + parts = view_rotation.split(",") + if len(parts) == 3: + params["viewRotation"] = [float(p.strip()) for p in parts] result = run_command("manage_scene", params, config) - click.echo(format_output(result, config.format)) - if result.get("success"): - print_success("Screenshot captured") + + if batch and result.get("success"): + data = result.get("data", {}) + count = len(data.get("screenshots", [])) + print_success(f"Captured {count} batch screenshots") + else: + click.echo(format_output(result, config.format)) + if result.get("success"): + print_success("Screenshot captured") diff --git a/Server/src/services/tools/manage_gameobject.py b/Server/src/services/tools/manage_gameobject.py index 2382fccbb..9664d1d23 100644 --- a/Server/src/services/tools/manage_gameobject.py +++ b/Server/src/services/tools/manage_gameobject.py @@ -41,7 +41,7 @@ def _normalize_component_properties(value: Any) -> tuple[dict[str, dict[str, Any @mcp_for_unity_tool( description=( "Performs CRUD operations on GameObjects. " - "Actions: create, modify, delete, duplicate, move_relative. " + "Actions: create, modify, delete, duplicate, move_relative, look_at. " "NOT for searching — use the find_gameobjects tool to search by name/tag/layer/component/path. " "NOT for component management — use the manage_components tool (add/remove/set_property) " "or mcpforunity://scene/gameobject/{id}/components resource (read)." @@ -54,7 +54,7 @@ def _normalize_component_properties(value: Any) -> tuple[dict[str, dict[str, Any async def manage_gameobject( ctx: Context, action: Annotated[Literal["create", "modify", "delete", "duplicate", - "move_relative"], "Action to perform on GameObject."] | None = None, + "move_relative", "look_at"], "Action to perform on GameObject."] | None = None, target: Annotated[str, "GameObject identifier by name, path, or instance ID for modify/delete/duplicate actions"] | None = None, search_method: Annotated[ @@ -109,6 +109,11 @@ async def manage_gameobject( "Distance to move in the specified direction (default: 1.0)"] | None = None, world_space: Annotated[bool | str, "If True (default), use world space directions; if False, use reference object's local directions"] | None = None, + # --- Parameters for 'look_at' --- + look_at_target: Annotated[list[float] | str, + "World position [x,y,z] or GameObject name/path/ID to look at (for look_at action)."] | None = None, + look_at_up: Annotated[list[float] | str, + "Optional up vector [x,y,z] for look_at. Defaults to [0,1,0]."] | None = None, ) -> dict[str, Any]: # Get active instance from session state # Removed session_state import @@ -121,7 +126,7 @@ async def manage_gameobject( if action is None: return { "success": False, - "message": "Missing required parameter 'action'. Valid actions: create, modify, delete, duplicate, move_relative. To SEARCH for GameObjects use the find_gameobjects tool. To manage COMPONENTS use the manage_components tool." + "message": "Missing required parameter 'action'. Valid actions: create, modify, delete, duplicate, move_relative, look_at. To SEARCH for GameObjects use the find_gameobjects tool. To manage COMPONENTS use the manage_components tool." } # --- Normalize vector parameters with detailed error handling --- @@ -187,6 +192,9 @@ async def manage_gameobject( "direction": direction, "distance": distance, "world_space": world_space, + # Parameters for 'look_at' + "look_at_target": look_at_target, + "look_at_up": look_at_up, } params = {k: v for k, v in params.items() if v is not None} diff --git a/Server/src/services/tools/manage_scene.py b/Server/src/services/tools/manage_scene.py index 2a29b906e..99d6944b7 100644 --- a/Server/src/services/tools/manage_scene.py +++ b/Server/src/services/tools/manage_scene.py @@ -1,18 +1,78 @@ +import json from typing import Annotated, Literal, Any from fastmcp import Context -from mcp.types import ToolAnnotations +from fastmcp.server.server import ToolResult +from mcp.types import ToolAnnotations, TextContent, ImageContent from services.registry import mcp_for_unity_tool from services.tools import get_unity_instance_from_context -from services.tools.utils import coerce_int, coerce_bool +from services.tools.utils import coerce_int, coerce_bool, normalize_vector3 from transport.unity_transport import send_with_unity_instance from transport.legacy.unity_connection import async_send_command_with_retry from services.tools.preflight import preflight +def _extract_images(response: dict[str, Any], action: str) -> ToolResult | None: + """If the Unity response contains inline base64 images, return a ToolResult + with TextContent + ImageContent blocks. Returns None for normal text-only responses.""" + if not isinstance(response, dict) or not response.get("success"): + return None + + data = response.get("data") + if not isinstance(data, dict): + return None + + if action == "screenshot": + # Batch images (surround mode) — multiple screenshots in one response + screenshots = data.get("screenshots") + if screenshots and isinstance(screenshots, list): + blocks: list[TextContent | ImageContent] = [] + summary_screenshots = [] + for s in screenshots: + summary_screenshots.append({k: v for k, v in s.items() if k != "imageBase64"}) + text_result = { + "success": True, + "message": response.get("message", ""), + "data": { + "sceneCenter": data.get("sceneCenter"), + "sceneRadius": data.get("sceneRadius"), + "screenshots": summary_screenshots, + }, + } + blocks.append(TextContent(type="text", text=json.dumps(text_result))) + for s in screenshots: + b64 = s.get("imageBase64") + if b64: + blocks.append(TextContent(type="text", text=f"[Angle: {s.get('angle', '?')}]")) + blocks.append(ImageContent(type="image", data=b64, mimeType="image/png")) + return ToolResult(content=blocks) + + # Single image (include_image or positioned capture) + image_b64 = data.get("imageBase64") + if not image_b64: + return None + text_data = {k: v for k, v in data.items() if k != "imageBase64"} + text_result = {"success": True, "message": response.get("message", ""), "data": text_data} + return ToolResult( + content=[ + TextContent(type="text", text=json.dumps(text_result)), + ImageContent(type="image", data=image_b64, mimeType="image/png"), + ], + ) + + return None + + @mcp_for_unity_tool( - description="Performs CRUD operations on Unity scenes. Read-only actions: get_hierarchy, get_active, get_build_settings, screenshot. Modifying actions: create, load, save.", + description=( + "Performs CRUD operations on Unity scenes. " + "Read-only actions: get_hierarchy, get_active, get_build_settings, screenshot, scene_view_frame. " + "Modifying actions: create, load, save. " + "screenshot supports include_image=true to return an inline base64 PNG for AI vision. " + "screenshot with batch='surround' captures 6 angles around the scene (no file saved) for comprehensive scene understanding. " + "screenshot with look_at/view_position creates a temp camera at that viewpoint and returns an inline image." + ), annotations=ToolAnnotations( title="Manage Scene", destructiveHint=True, @@ -28,15 +88,39 @@ async def manage_scene( "get_active", "get_build_settings", "screenshot", - ], "Perform CRUD operations on Unity scenes, and capture a screenshot."], + "scene_view_frame", + ], "Perform CRUD operations on Unity scenes, capture screenshots, and control the Scene View camera."], name: Annotated[str, "Scene name."] | None = None, path: Annotated[str, "Scene path."] | None = None, build_index: Annotated[int | str, "Unity build index (quote as string, e.g., '0')."] | None = None, + # --- screenshot params --- screenshot_file_name: Annotated[str, "Screenshot file name (optional). Defaults to timestamp when omitted."] | None = None, screenshot_super_size: Annotated[int | str, "Screenshot supersize multiplier (integer ≥1). Optional."] | None = None, + camera: Annotated[str, + "Camera to capture from (name, path, or instance ID). Defaults to Camera.main."] | None = None, + include_image: Annotated[bool | str, + "If true, return the screenshot as an inline base64 PNG image in the response. " + "The AI can see the image. Default false. Recommended max_resolution=512 for context efficiency."] | None = None, + max_resolution: Annotated[int | str, + "Max resolution (longest edge in pixels) for the inline image. Default 640. " + "Use 256-512 for quick looks, 640-1024 for detail."] | None = None, + # --- screenshot extended params (batch, positioned capture) --- + batch: Annotated[str, + "Batch capture mode. 'surround' captures 6 angles (front/back/left/right/top/bird_eye) " + "around the scene or look_at target. Returns inline images, no file saved."] | None = None, + look_at: Annotated[str | int | list[float], + "Target to aim the camera at before capture. Can be a GameObject name/path/ID or [x,y,z] position. " + "For batch='surround', centers the surround on this target. For single shots, creates a temp camera aimed here."] | None = None, + view_position: Annotated[list[float] | str, + "World position [x,y,z] to place the camera for a positioned screenshot."] | None = None, + view_rotation: Annotated[list[float] | str, + "Euler rotation [x,y,z] for the camera. Overrides look_at aiming if both provided."] | None = None, + # --- scene_view_frame params --- + scene_view_target: Annotated[str | int, + "GameObject reference for scene_view_frame (name, path, or instance ID)."] | None = None, # --- get_hierarchy paging/safety --- parent: Annotated[str | int, "Optional parent GameObject reference (name/path/instanceID) to list direct children."] | None = None, @@ -52,9 +136,7 @@ async def manage_scene( "Child paging hint (safety)."] | None = None, include_transform: Annotated[bool | str, "If true, include local transform in node summaries."] | None = None, -) -> dict[str, Any]: - # Get active instance from session state - # Removed session_state import +) -> dict[str, Any] | ToolResult: unity_instance = get_unity_instance_from_context(ctx) gate = await preflight(ctx, wait_for_no_compile=True, refresh_if_dirty=True) if gate is not None: @@ -70,6 +152,10 @@ async def manage_scene( max_children_per_node, default=None) coerced_include_transform = coerce_bool( include_transform, default=None) + coerced_include_image = coerce_bool(include_image, default=None) + coerced_max_resolution = coerce_int(max_resolution, default=None) + if coerced_max_resolution is not None and coerced_max_resolution <= 0: + return {"success": False, "message": "max_resolution must be a positive integer greater than zero."} params: dict[str, Any] = {"action": action} if name: @@ -83,6 +169,34 @@ async def manage_scene( if coerced_super_size is not None: params["superSize"] = coerced_super_size + # screenshot params + if camera: + params["camera"] = camera + if coerced_include_image is not None: + params["includeImage"] = coerced_include_image + if coerced_max_resolution is not None: + params["maxResolution"] = coerced_max_resolution + + # screenshot extended params (batch, positioned capture) + if batch: + params["batch"] = batch + if look_at is not None: + params["lookAt"] = look_at + if view_position is not None: + vec, err = normalize_vector3(view_position, "view_position") + if err: + return {"success": False, "message": err} + params["viewPosition"] = vec + if view_rotation is not None: + vec, err = normalize_vector3(view_rotation, "view_rotation") + if err: + return {"success": False, "message": err} + params["viewRotation"] = vec + + # scene_view_frame params + if scene_view_target is not None: + params["sceneViewTarget"] = scene_view_target + # get_hierarchy paging/safety params (optional) if parent is not None: params["parent"] = parent @@ -104,7 +218,14 @@ async def manage_scene( # Preserve structured failure data; unwrap success into a friendlier shape if isinstance(response, dict) and response.get("success"): - return {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")} + friendly = {"success": True, "message": response.get("message", "Scene operation successful."), "data": response.get("data")} + + # For screenshot actions, check if inline images should be returned as ImageContent + image_result = _extract_images(response, action) + if image_result is not None: + return image_result + + return friendly return response if isinstance(response, dict) else {"success": False, "message": str(response)} except Exception as e: diff --git a/Server/tests/integration/conftest.py b/Server/tests/integration/conftest.py index 216ca946a..700156ca4 100644 --- a/Server/tests/integration/conftest.py +++ b/Server/tests/integration/conftest.py @@ -76,19 +76,57 @@ class _DummyMiddlewareContext: pass +class _DummyToolResult: + """Stub for fastmcp.server.server.ToolResult""" + def __init__(self, content=None, is_error=False): + self.content = content or [] + self.is_error = is_error + + fastmcp.FastMCP = _DummyFastMCP fastmcp.Context = _DummyContext sys.modules.setdefault("fastmcp", fastmcp) -# Stub fastmcp.server.middleware submodule +# Stub fastmcp.server, fastmcp.server.middleware, fastmcp.server.server submodules fastmcp_server = types.ModuleType("fastmcp.server") fastmcp_server_middleware = types.ModuleType("fastmcp.server.middleware") fastmcp_server_middleware.Middleware = _DummyMiddleware fastmcp_server_middleware.MiddlewareContext = _DummyMiddlewareContext +fastmcp_server_server = types.ModuleType("fastmcp.server.server") +fastmcp_server_server.ToolResult = _DummyToolResult fastmcp.server = fastmcp_server fastmcp_server.middleware = fastmcp_server_middleware +fastmcp_server.server = fastmcp_server_server sys.modules.setdefault("fastmcp.server", fastmcp_server) sys.modules.setdefault("fastmcp.server.middleware", fastmcp_server_middleware) +sys.modules.setdefault("fastmcp.server.server", fastmcp_server_server) + +# Stub mcp.types for TextContent, ImageContent, ToolAnnotations +_mcp_types = sys.modules.get("mcp.types") +if _mcp_types is None: + _mcp_mod = sys.modules.setdefault("mcp", types.ModuleType("mcp")) + _mcp_types = types.ModuleType("mcp.types") + + class _TextContent: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + class _ImageContent: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + class _ToolAnnotations: + def __init__(self, **kwargs): + for k, v in kwargs.items(): + setattr(self, k, v) + + _mcp_types.TextContent = _TextContent + _mcp_types.ImageContent = _ImageContent + _mcp_types.ToolAnnotations = _ToolAnnotations + _mcp_mod.types = _mcp_types + sys.modules["mcp.types"] = _mcp_types # Note: starlette is now a proper dependency (via mcp package), so we don't stub it anymore. # The real starlette package will be imported when needed. diff --git a/Server/tests/integration/test_logging_stdout.py b/Server/tests/integration/test_logging_stdout.py index 4314e4121..3714a94ff 100644 --- a/Server/tests/integration/test_logging_stdout.py +++ b/Server/tests/integration/test_logging_stdout.py @@ -20,6 +20,10 @@ def test_no_print_statements_in_codebase(): """Ensure no stray print/sys.stdout writes remain in server source.""" + # CLI tools that intentionally print to stdout + ALLOWED_PRINT_FILES = { + Path("scene_generator") / "test_pipeline.py", + } offenders = [] syntax_errors = [] for py_file in SRC.rglob("*.py"): @@ -56,8 +60,9 @@ def visit_Call(self, node: ast.Call): v = StdoutVisitor() v.visit(tree) - if v.hit: - offenders.append(py_file.relative_to(SRC)) + rel_path = py_file.relative_to(SRC) + if v.hit and rel_path not in ALLOWED_PRINT_FILES: + offenders.append(rel_path) assert not syntax_errors, "syntax errors in: " + \ ", ".join(str(e) for e in syntax_errors) assert not offenders, "stdout writes found in: " + \ diff --git a/Server/tests/integration/test_manage_gameobject_look_at.py b/Server/tests/integration/test_manage_gameobject_look_at.py new file mode 100644 index 000000000..6783661a6 --- /dev/null +++ b/Server/tests/integration/test_manage_gameobject_look_at.py @@ -0,0 +1,77 @@ +import pytest + +from .test_helpers import DummyContext +import services.tools.manage_gameobject as manage_go_mod + + +@pytest.mark.asyncio +async def test_look_at_vector_target(monkeypatch): + """look_at action forwards look_at_target as a vector.""" + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": True, "data": {"rotation": [0, 90, 0]}} + + monkeypatch.setattr(manage_go_mod, "async_send_command_with_retry", fake_send) + + resp = await manage_go_mod.manage_gameobject( + ctx=DummyContext(), + action="look_at", + target="MainCamera", + look_at_target=[10.0, 0.0, 5.0], + ) + + assert resp.get("success") is True + p = captured["params"] + assert p["action"] == "look_at" + assert p["target"] == "MainCamera" + assert p["look_at_target"] == [10.0, 0.0, 5.0] + + +@pytest.mark.asyncio +async def test_look_at_string_target(monkeypatch): + """look_at action forwards look_at_target as a GO name string.""" + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": True, "data": {"rotation": [0, 45, 0]}} + + monkeypatch.setattr(manage_go_mod, "async_send_command_with_retry", fake_send) + + resp = await manage_go_mod.manage_gameobject( + ctx=DummyContext(), + action="look_at", + target="MainCamera", + look_at_target="Player", + look_at_up=[0, 1, 0], + ) + + assert resp.get("success") is True + p = captured["params"] + assert p["action"] == "look_at" + assert p["look_at_target"] == "Player" + assert p["look_at_up"] == [0, 1, 0] + + +@pytest.mark.asyncio +async def test_look_at_without_target_still_sends(monkeypatch): + """look_at without look_at_target should still send the command (C# will error).""" + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": False, "message": "look_at_target is required"} + + monkeypatch.setattr(manage_go_mod, "async_send_command_with_retry", fake_send) + + resp = await manage_go_mod.manage_gameobject( + ctx=DummyContext(), + action="look_at", + target="MainCamera", + ) + + p = captured["params"] + assert p["action"] == "look_at" + assert "look_at_target" not in p diff --git a/Server/tests/integration/test_manage_scene_screenshot_params.py b/Server/tests/integration/test_manage_scene_screenshot_params.py new file mode 100644 index 000000000..dce067717 --- /dev/null +++ b/Server/tests/integration/test_manage_scene_screenshot_params.py @@ -0,0 +1,258 @@ +import pytest + +from .test_helpers import DummyContext +import services.tools.manage_scene as manage_scene_mod +from services.tools.manage_scene import _extract_images + + +# --------------------------------------------------------------------------- +# _extract_images unit tests +# --------------------------------------------------------------------------- + +def test_extract_images_returns_none_for_non_dict(): + assert _extract_images("not a dict", "screenshot") is None + + +def test_extract_images_returns_none_for_failed_response(): + assert _extract_images({"success": False}, "screenshot") is None + + +def test_extract_images_returns_none_when_no_base64(): + resp = {"success": True, "data": {"filePath": "Assets/shot.png"}} + assert _extract_images(resp, "screenshot") is None + + +def test_extract_images_screenshot_returns_tool_result(): + resp = { + "success": True, + "message": "ok", + "data": { + "filePath": "Assets/shot.png", + "imageBase64": "iVBOR_FAKE_PNG_DATA", + "imageWidth": 512, + "imageHeight": 512, + }, + } + result = _extract_images(resp, "screenshot") + assert result is not None + # Should have TextContent + ImageContent + assert len(result.content) == 2 + assert result.content[0].type == "text" + assert result.content[1].type == "image" + assert result.content[1].data == "iVBOR_FAKE_PNG_DATA" + assert result.content[1].mimeType == "image/png" + # Text block should NOT contain base64 + assert "iVBOR_FAKE_PNG_DATA" not in result.content[0].text + + +def test_extract_images_batch_surround_returns_tool_result(): + resp = { + "success": True, + "message": "ok", + "data": { + "sceneCenter": [0, 0, 0], + "sceneRadius": 10.0, + "screenshots": [ + {"angle": "front", "imageBase64": "FRONT64", "imageWidth": 256, "imageHeight": 256}, + {"angle": "back", "imageBase64": "BACK64", "imageWidth": 256, "imageHeight": 256}, + ], + }, + } + result = _extract_images(resp, "screenshot") + assert result is not None + # 1 text summary + 2*(label + image) = 5 blocks + assert len(result.content) == 5 + assert result.content[0].type == "text" + assert result.content[1].type == "text" # angle label + assert result.content[2].type == "image" + assert result.content[2].data == "FRONT64" + assert result.content[3].type == "text" # angle label + assert result.content[4].type == "image" + assert result.content[4].data == "BACK64" + # Text summary should NOT contain base64 + assert "FRONT64" not in result.content[0].text + + +def test_extract_images_batch_no_screenshots(): + resp = {"success": True, "data": {"screenshots": []}} + assert _extract_images(resp, "screenshot") is None + + +def test_extract_images_positioned_returns_tool_result(): + resp = { + "success": True, + "message": "ok", + "data": { + "imageBase64": "VIEW_B64", + "imageWidth": 640, + "imageHeight": 480, + "viewPosition": [0, 10, -10], + "lookAt": [0, 0, 0], + }, + } + result = _extract_images(resp, "screenshot") + assert result is not None + assert len(result.content) == 2 + assert result.content[1].data == "VIEW_B64" + + +def test_extract_images_unknown_action(): + resp = {"success": True, "data": {"imageBase64": "STUFF"}} + assert _extract_images(resp, "get_hierarchy") is None + + +# --------------------------------------------------------------------------- +# manage_scene param pass-through tests +# --------------------------------------------------------------------------- + +@pytest.mark.asyncio +async def test_screenshot_camera_and_include_image_params(monkeypatch): + """New camera, include_image, and max_resolution params are forwarded.""" + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": True, "data": {"filePath": "Assets/shot.png"}} + + monkeypatch.setattr(manage_scene_mod, "async_send_command_with_retry", fake_send) + + resp = await manage_scene_mod.manage_scene( + ctx=DummyContext(), + action="screenshot", + camera="MainCamera", + include_image=True, + max_resolution=256, + ) + + p = captured["params"] + assert p["action"] == "screenshot" + assert p["camera"] == "MainCamera" + assert p["includeImage"] is True + assert p["maxResolution"] == 256 + + +@pytest.mark.asyncio +async def test_screenshot_batch_surround_params(monkeypatch): + """batch='surround' and max_resolution are forwarded.""" + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": True, "data": {"screenshots": []}} + + monkeypatch.setattr(manage_scene_mod, "async_send_command_with_retry", fake_send) + + resp = await manage_scene_mod.manage_scene( + ctx=DummyContext(), + action="screenshot", + batch="surround", + max_resolution=128, + ) + + p = captured["params"] + assert p["action"] == "screenshot" + assert p["batch"] == "surround" + assert p["maxResolution"] == 128 + + +@pytest.mark.asyncio +async def test_screenshot_positioned_params(monkeypatch): + """look_at and view_position params are forwarded.""" + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": True, "data": {}} + + monkeypatch.setattr(manage_scene_mod, "async_send_command_with_retry", fake_send) + + await manage_scene_mod.manage_scene( + ctx=DummyContext(), + action="screenshot", + look_at="Player", + view_position=[0, 10, -10], + view_rotation=[45, 0, 0], + max_resolution=512, + ) + + p = captured["params"] + assert p["action"] == "screenshot" + assert p["lookAt"] == "Player" + assert p["viewPosition"] == [0, 10, -10] + assert p["viewRotation"] == [45, 0, 0] + assert p["maxResolution"] == 512 + + +@pytest.mark.asyncio +async def test_screenshot_batch_with_look_at_params(monkeypatch): + """batch='surround' + look_at centers surround on the target.""" + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": True, "data": {"screenshots": []}} + + monkeypatch.setattr(manage_scene_mod, "async_send_command_with_retry", fake_send) + + await manage_scene_mod.manage_scene( + ctx=DummyContext(), + action="screenshot", + batch="surround", + look_at="Enemy", + max_resolution=256, + ) + + p = captured["params"] + assert p["action"] == "screenshot" + assert p["batch"] == "surround" + assert p["lookAt"] == "Enemy" + + +@pytest.mark.asyncio +async def test_scene_view_frame_params(monkeypatch): + captured = {} + + async def fake_send(cmd, params, **kwargs): + captured["params"] = params + return {"success": True, "data": {}} + + monkeypatch.setattr(manage_scene_mod, "async_send_command_with_retry", fake_send) + + await manage_scene_mod.manage_scene( + ctx=DummyContext(), + action="scene_view_frame", + scene_view_target="Player", + ) + + p = captured["params"] + assert p["action"] == "scene_view_frame" + assert p["sceneViewTarget"] == "Player" + + +@pytest.mark.asyncio +async def test_screenshot_returns_tool_result_with_image(monkeypatch): + """When Unity returns imageBase64, manage_scene should return a ToolResult.""" + + async def fake_send(cmd, params, **kwargs): + return { + "success": True, + "message": "ok", + "data": { + "filePath": "Assets/shot.png", + "imageBase64": "FAKE_B64", + "imageWidth": 256, + "imageHeight": 256, + }, + } + + monkeypatch.setattr(manage_scene_mod, "async_send_command_with_retry", fake_send) + + result = await manage_scene_mod.manage_scene( + ctx=DummyContext(), + action="screenshot", + include_image=True, + ) + + from fastmcp.server.server import ToolResult + assert isinstance(result, ToolResult) + assert any(getattr(c, "data", None) == "FAKE_B64" for c in result.content) diff --git a/Server/uv.lock b/Server/uv.lock index 46e8a45a7..db58320c7 100644 --- a/Server/uv.lock +++ b/Server/uv.lock @@ -1,6 +1,34 @@ version = 1 revision = 3 requires-python = ">=3.10" +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.11'", +] + +[[package]] +name = "altair" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "narwhals" }, + { name = "packaging" }, + { name = "typing-extensions", marker = "python_full_version < '3.15'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/c0/184a89bd5feba14ff3c41cfaf1dd8a82c05f5ceedbc92145e17042eb08a4/altair-6.0.0.tar.gz", hash = "sha256:614bf5ecbe2337347b590afb111929aa9c16c9527c4887d96c9bc7f6640756b4", size = 763834, upload-time = "2025-11-12T08:59:11.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/33/ef2f2409450ef6daa61459d5de5c08128e7d3edb773fefd0a324d1310238/altair-6.0.0-py3-none-any.whl", hash = "sha256:09ae95b53d5fe5b16987dccc785a7af8588f2dca50de1e7a156efa8a461515f8", size = 795410, upload-time = "2025-11-12T08:59:09.804Z" }, +] [[package]] name = "annotated-doc" @@ -20,6 +48,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "anthropic" +version = "0.79.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "docstring-parser" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/b1/91aea3f8fd180d01d133d931a167a78a3737b3fd39ccef2ae8d6619c24fd/anthropic-0.79.0.tar.gz", hash = "sha256:8707aafb3b1176ed6c13e2b1c9fb3efddce90d17aee5d8b83a86c70dcdcca871", size = 509825, upload-time = "2026-02-07T18:06:18.388Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/b2/cc0b8e874a18d7da50b0fda8c99e4ac123f23bf47b471827c5f6f3e4a767/anthropic-0.79.0-py3-none-any.whl", hash = "sha256:04cbd473b6bbda4ca2e41dd670fe2f829a911530f01697d0a1e37321eb75f3cf", size = 405918, upload-time = "2026-02-07T18:06:20.246Z" }, +] + [[package]] name = "anyio" version = "4.12.1" @@ -91,6 +138,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" }, ] +[[package]] +name = "blinker" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460, upload-time = "2024-11-08T17:25:47.436Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, +] + [[package]] name = "cachetools" version = "6.2.4" @@ -505,6 +561,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, ] +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -618,6 +683,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1d/82/72401d09dc27c27fdf72ad6c2fe331e553e3c3646e01b5ff16473191033d/fastmcp-2.14.1-py3-none-any.whl", hash = "sha256:fb3e365cc1d52573ab89caeba9944dd4b056149097be169bce428e011f0a57e5", size = 412176, upload-time = "2025-12-15T02:26:25.356Z" }, ] +[[package]] +name = "gitdb" +version = "4.0.12" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "smmap" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, +] + +[[package]] +name = "gitpython" +version = "3.1.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "gitdb" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, +] + [[package]] name = "h11" version = "0.16.0" @@ -739,6 +828,115 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010, upload-time = "2025-02-27T18:51:00.104Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + +[[package]] +name = "jiter" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/5a/41da76c5ea07bec1b0472b6b2fdb1b651074d504b19374d7e130e0cdfb25/jiter-0.13.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2ffc63785fd6c7977defe49b9824ae6ce2b2e2b77ce539bdaf006c26da06342e", size = 311164, upload-time = "2026-02-02T12:35:17.688Z" }, + { url = "https://files.pythonhosted.org/packages/40/cb/4a1bf994a3e869f0d39d10e11efb471b76d0ad70ecbfb591427a46c880c2/jiter-0.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4a638816427006c1e3f0013eb66d391d7a3acda99a7b0cf091eff4497ccea33a", size = 320296, upload-time = "2026-02-02T12:35:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/09/82/acd71ca9b50ecebadc3979c541cd717cce2fe2bc86236f4fa597565d8f1a/jiter-0.13.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19928b5d1ce0ff8c1ee1b9bdef3b5bfc19e8304f1b904e436caf30bc15dc6cf5", size = 352742, upload-time = "2026-02-02T12:35:21.258Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/d1fc996f3aecfd42eb70922edecfb6dd26421c874503e241153ad41df94f/jiter-0.13.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:309549b778b949d731a2f0e1594a3f805716be704a73bf3ad9a807eed5eb5721", size = 363145, upload-time = "2026-02-02T12:35:24.653Z" }, + { url = "https://files.pythonhosted.org/packages/f1/61/a30492366378cc7a93088858f8991acd7d959759fe6138c12a4644e58e81/jiter-0.13.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcdabaea26cb04e25df3103ce47f97466627999260290349a88c8136ecae0060", size = 487683, upload-time = "2026-02-02T12:35:26.162Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/4223cffa9dbbbc96ed821c5aeb6bca510848c72c02086d1ed3f1da3d58a7/jiter-0.13.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a377af27b236abbf665a69b2bdd680e3b5a0bd2af825cd3b81245279a7606c", size = 373579, upload-time = "2026-02-02T12:35:27.582Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c9/b0489a01329ab07a83812d9ebcffe7820a38163c6d9e7da644f926ff877c/jiter-0.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe49d3ff6db74321f144dff9addd4a5874d3105ac5ba7c5b77fac099cfae31ae", size = 362904, upload-time = "2026-02-02T12:35:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/05/af/53e561352a44afcba9a9bc67ee1d320b05a370aed8df54eafe714c4e454d/jiter-0.13.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2113c17c9a67071b0f820733c0893ed1d467b5fcf4414068169e5c2cabddb1e2", size = 392380, upload-time = "2026-02-02T12:35:30.385Z" }, + { url = "https://files.pythonhosted.org/packages/76/2a/dd805c3afb8ed5b326c5ae49e725d1b1255b9754b1b77dbecdc621b20773/jiter-0.13.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ab1185ca5c8b9491b55ebf6c1e8866b8f68258612899693e24a92c5fdb9455d5", size = 517939, upload-time = "2026-02-02T12:35:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/20/2a/7b67d76f55b8fe14c937e7640389612f05f9a4145fc28ae128aaa5e62257/jiter-0.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:9621ca242547edc16400981ca3231e0c91c0c4c1ab8573a596cd9bb3575d5c2b", size = 551696, upload-time = "2026-02-02T12:35:33.306Z" }, + { url = "https://files.pythonhosted.org/packages/85/9c/57cdd64dac8f4c6ab8f994fe0eb04dc9fd1db102856a4458fcf8a99dfa62/jiter-0.13.0-cp310-cp310-win32.whl", hash = "sha256:a7637d92b1c9d7a771e8c56f445c7f84396d48f2e756e5978840ecba2fac0894", size = 204592, upload-time = "2026-02-02T12:35:34.58Z" }, + { url = "https://files.pythonhosted.org/packages/a7/38/f4f3ea5788b8a5bae7510a678cdc747eda0c45ffe534f9878ff37e7cf3b3/jiter-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c1b609e5cbd2f52bb74fb721515745b407df26d7b800458bd97cb3b972c29e7d", size = 206016, upload-time = "2026-02-02T12:35:36.435Z" }, + { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, + { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, + { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, + { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, + { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, + { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, + { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, +] + [[package]] name = "jsonschema" version = "4.26.0" @@ -885,6 +1083,91 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + [[package]] name = "mcp" version = "1.25.0" @@ -931,22 +1214,32 @@ dev = [ { name = "pytest-asyncio" }, { name = "pytest-cov" }, ] +gui = [ + { name = "anthropic" }, + { name = "openai" }, + { name = "pandas" }, + { name = "streamlit" }, +] [package.metadata] requires-dist = [ + { name = "anthropic", marker = "extra == 'gui'", specifier = ">=0.18.0" }, { name = "click", specifier = ">=8.1.0" }, { name = "fastapi", specifier = ">=0.104.0" }, { name = "fastmcp", specifier = "==2.14.1" }, { name = "httpx", specifier = ">=0.27.2" }, { name = "mcp", specifier = ">=1.16.0" }, + { name = "openai", marker = "extra == 'gui'", specifier = ">=1.0.0" }, + { name = "pandas", marker = "extra == 'gui'", specifier = ">=2.0.0" }, { name = "pydantic", specifier = ">=2.12.5" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8.0.0" }, { name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23" }, { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.1.0" }, + { name = "streamlit", marker = "extra == 'gui'", specifier = ">=1.30.0" }, { name = "tomli", specifier = ">=2.3.0" }, { name = "uvicorn", specifier = ">=0.35.0" }, ] -provides-extras = ["dev"] +provides-extras = ["dev", "gui"] [[package]] name = "mdurl" @@ -966,6 +1259,189 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, ] +[[package]] +name = "narwhals" +version = "2.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6f/713be67779028d482c6e0f2dde5bc430021b2578a4808c1c9f6d7ad48257/narwhals-2.16.0.tar.gz", hash = "sha256:155bb45132b370941ba0396d123cf9ed192bf25f39c4cea726f2da422ca4e145", size = 618268, upload-time = "2026-02-02T10:31:00.545Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/cc/7cb74758e6df95e0c4e1253f203b6dd7f348bf2f29cf89e9210a2416d535/narwhals-2.16.0-py3-none-any.whl", hash = "sha256:846f1fd7093ac69d63526e50732033e86c30ea0026a44d9b23991010c7d1485d", size = 443951, upload-time = "2026-02-02T10:30:58.635Z" }, +] + +[[package]] +name = "numpy" +version = "2.2.6" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.11'", +] +sdist = { url = "https://files.pythonhosted.org/packages/76/21/7d2a95e4bba9dc13d043ee156a356c0a8f0c6309dff6b21b4d71a073b8a8/numpy-2.2.6.tar.gz", hash = "sha256:e29554e2bef54a90aa5cc07da6ce955accb83f21ab5de01a62c8478897b264fd", size = 20276440, upload-time = "2025-05-17T22:38:04.611Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3e/ed6db5be21ce87955c0cbd3009f2803f59fa08df21b5df06862e2d8e2bdd/numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb", size = 21165245, upload-time = "2025-05-17T21:27:58.555Z" }, + { url = "https://files.pythonhosted.org/packages/22/c2/4b9221495b2a132cc9d2eb862e21d42a009f5a60e45fc44b00118c174bff/numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90", size = 14360048, upload-time = "2025-05-17T21:28:21.406Z" }, + { url = "https://files.pythonhosted.org/packages/fd/77/dc2fcfc66943c6410e2bf598062f5959372735ffda175b39906d54f02349/numpy-2.2.6-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:37e990a01ae6ec7fe7fa1c26c55ecb672dd98b19c3d0e1d1f326fa13cb38d163", size = 5340542, upload-time = "2025-05-17T21:28:30.931Z" }, + { url = "https://files.pythonhosted.org/packages/7a/4f/1cb5fdc353a5f5cc7feb692db9b8ec2c3d6405453f982435efc52561df58/numpy-2.2.6-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:5a6429d4be8ca66d889b7cf70f536a397dc45ba6faeb5f8c5427935d9592e9cf", size = 6878301, upload-time = "2025-05-17T21:28:41.613Z" }, + { url = "https://files.pythonhosted.org/packages/eb/17/96a3acd228cec142fcb8723bd3cc39c2a474f7dcf0a5d16731980bcafa95/numpy-2.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:efd28d4e9cd7d7a8d39074a4d44c63eda73401580c5c76acda2ce969e0a38e83", size = 14297320, upload-time = "2025-05-17T21:29:02.78Z" }, + { url = "https://files.pythonhosted.org/packages/b4/63/3de6a34ad7ad6646ac7d2f55ebc6ad439dbbf9c4370017c50cf403fb19b5/numpy-2.2.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc7b73d02efb0e18c000e9ad8b83480dfcd5dfd11065997ed4c6747470ae8915", size = 16801050, upload-time = "2025-05-17T21:29:27.675Z" }, + { url = "https://files.pythonhosted.org/packages/07/b6/89d837eddef52b3d0cec5c6ba0456c1bf1b9ef6a6672fc2b7873c3ec4e2e/numpy-2.2.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:74d4531beb257d2c3f4b261bfb0fc09e0f9ebb8842d82a7b4209415896adc680", size = 15807034, upload-time = "2025-05-17T21:29:51.102Z" }, + { url = "https://files.pythonhosted.org/packages/01/c8/dc6ae86e3c61cfec1f178e5c9f7858584049b6093f843bca541f94120920/numpy-2.2.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8fc377d995680230e83241d8a96def29f204b5782f371c532579b4f20607a289", size = 18614185, upload-time = "2025-05-17T21:30:18.703Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c5/0064b1b7e7c89137b471ccec1fd2282fceaae0ab3a9550f2568782d80357/numpy-2.2.6-cp310-cp310-win32.whl", hash = "sha256:b093dd74e50a8cba3e873868d9e93a85b78e0daf2e98c6797566ad8044e8363d", size = 6527149, upload-time = "2025-05-17T21:30:29.788Z" }, + { url = "https://files.pythonhosted.org/packages/a3/dd/4b822569d6b96c39d1215dbae0582fd99954dcbcf0c1a13c61783feaca3f/numpy-2.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:f0fd6321b839904e15c46e0d257fdd101dd7f530fe03fd6359c1ea63738703f3", size = 12904620, upload-time = "2025-05-17T21:30:48.994Z" }, + { url = "https://files.pythonhosted.org/packages/da/a8/4f83e2aa666a9fbf56d6118faaaf5f1974d456b1823fda0a176eff722839/numpy-2.2.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f9f1adb22318e121c5c69a09142811a201ef17ab257a1e66ca3025065b7f53ae", size = 21176963, upload-time = "2025-05-17T21:31:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/b3/2b/64e1affc7972decb74c9e29e5649fac940514910960ba25cd9af4488b66c/numpy-2.2.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c820a93b0255bc360f53eca31a0e676fd1101f673dda8da93454a12e23fc5f7a", size = 14406743, upload-time = "2025-05-17T21:31:41.087Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9f/0121e375000b5e50ffdd8b25bf78d8e1a5aa4cca3f185d41265198c7b834/numpy-2.2.6-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3d70692235e759f260c3d837193090014aebdf026dfd167834bcba43e30c2a42", size = 5352616, upload-time = "2025-05-17T21:31:50.072Z" }, + { url = "https://files.pythonhosted.org/packages/31/0d/b48c405c91693635fbe2dcd7bc84a33a602add5f63286e024d3b6741411c/numpy-2.2.6-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:481b49095335f8eed42e39e8041327c05b0f6f4780488f61286ed3c01368d491", size = 6889579, upload-time = "2025-05-17T21:32:01.712Z" }, + { url = "https://files.pythonhosted.org/packages/52/b8/7f0554d49b565d0171eab6e99001846882000883998e7b7d9f0d98b1f934/numpy-2.2.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b64d8d4d17135e00c8e346e0a738deb17e754230d7e0810ac5012750bbd85a5a", size = 14312005, upload-time = "2025-05-17T21:32:23.332Z" }, + { url = "https://files.pythonhosted.org/packages/b3/dd/2238b898e51bd6d389b7389ffb20d7f4c10066d80351187ec8e303a5a475/numpy-2.2.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba10f8411898fc418a521833e014a77d3ca01c15b0c6cdcce6a0d2897e6dbbdf", size = 16821570, upload-time = "2025-05-17T21:32:47.991Z" }, + { url = "https://files.pythonhosted.org/packages/83/6c/44d0325722cf644f191042bf47eedad61c1e6df2432ed65cbe28509d404e/numpy-2.2.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bd48227a919f1bafbdda0583705e547892342c26fb127219d60a5c36882609d1", size = 15818548, upload-time = "2025-05-17T21:33:11.728Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9d/81e8216030ce66be25279098789b665d49ff19eef08bfa8cb96d4957f422/numpy-2.2.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9551a499bf125c1d4f9e250377c1ee2eddd02e01eac6644c080162c0c51778ab", size = 18620521, upload-time = "2025-05-17T21:33:39.139Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fd/e19617b9530b031db51b0926eed5345ce8ddc669bb3bc0044b23e275ebe8/numpy-2.2.6-cp311-cp311-win32.whl", hash = "sha256:0678000bb9ac1475cd454c6b8c799206af8107e310843532b04d49649c717a47", size = 6525866, upload-time = "2025-05-17T21:33:50.273Z" }, + { url = "https://files.pythonhosted.org/packages/31/0a/f354fb7176b81747d870f7991dc763e157a934c717b67b58456bc63da3df/numpy-2.2.6-cp311-cp311-win_amd64.whl", hash = "sha256:e8213002e427c69c45a52bbd94163084025f533a55a59d6f9c5b820774ef3303", size = 12907455, upload-time = "2025-05-17T21:34:09.135Z" }, + { url = "https://files.pythonhosted.org/packages/82/5d/c00588b6cf18e1da539b45d3598d3557084990dcc4331960c15ee776ee41/numpy-2.2.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41c5a21f4a04fa86436124d388f6ed60a9343a6f767fced1a8a71c3fbca038ff", size = 20875348, upload-time = "2025-05-17T21:34:39.648Z" }, + { url = "https://files.pythonhosted.org/packages/66/ee/560deadcdde6c2f90200450d5938f63a34b37e27ebff162810f716f6a230/numpy-2.2.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de749064336d37e340f640b05f24e9e3dd678c57318c7289d222a8a2f543e90c", size = 14119362, upload-time = "2025-05-17T21:35:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/3c/65/4baa99f1c53b30adf0acd9a5519078871ddde8d2339dc5a7fde80d9d87da/numpy-2.2.6-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:894b3a42502226a1cac872f840030665f33326fc3dac8e57c607905773cdcde3", size = 5084103, upload-time = "2025-05-17T21:35:10.622Z" }, + { url = "https://files.pythonhosted.org/packages/cc/89/e5a34c071a0570cc40c9a54eb472d113eea6d002e9ae12bb3a8407fb912e/numpy-2.2.6-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:71594f7c51a18e728451bb50cc60a3ce4e6538822731b2933209a1f3614e9282", size = 6625382, upload-time = "2025-05-17T21:35:21.414Z" }, + { url = "https://files.pythonhosted.org/packages/f8/35/8c80729f1ff76b3921d5c9487c7ac3de9b2a103b1cd05e905b3090513510/numpy-2.2.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2618db89be1b4e05f7a1a847a9c1c0abd63e63a1607d892dd54668dd92faf87", size = 14018462, upload-time = "2025-05-17T21:35:42.174Z" }, + { url = "https://files.pythonhosted.org/packages/8c/3d/1e1db36cfd41f895d266b103df00ca5b3cbe965184df824dec5c08c6b803/numpy-2.2.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd83c01228a688733f1ded5201c678f0c53ecc1006ffbc404db9f7a899ac6249", size = 16527618, upload-time = "2025-05-17T21:36:06.711Z" }, + { url = "https://files.pythonhosted.org/packages/61/c6/03ed30992602c85aa3cd95b9070a514f8b3c33e31124694438d88809ae36/numpy-2.2.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:37c0ca431f82cd5fa716eca9506aefcabc247fb27ba69c5062a6d3ade8cf8f49", size = 15505511, upload-time = "2025-05-17T21:36:29.965Z" }, + { url = "https://files.pythonhosted.org/packages/b7/25/5761d832a81df431e260719ec45de696414266613c9ee268394dd5ad8236/numpy-2.2.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fe27749d33bb772c80dcd84ae7e8df2adc920ae8297400dabec45f0dedb3f6de", size = 18313783, upload-time = "2025-05-17T21:36:56.883Z" }, + { url = "https://files.pythonhosted.org/packages/57/0a/72d5a3527c5ebffcd47bde9162c39fae1f90138c961e5296491ce778e682/numpy-2.2.6-cp312-cp312-win32.whl", hash = "sha256:4eeaae00d789f66c7a25ac5f34b71a7035bb474e679f410e5e1a94deb24cf2d4", size = 6246506, upload-time = "2025-05-17T21:37:07.368Z" }, + { url = "https://files.pythonhosted.org/packages/36/fa/8c9210162ca1b88529ab76b41ba02d433fd54fecaf6feb70ef9f124683f1/numpy-2.2.6-cp312-cp312-win_amd64.whl", hash = "sha256:c1f9540be57940698ed329904db803cf7a402f3fc200bfe599334c9bd84a40b2", size = 12614190, upload-time = "2025-05-17T21:37:26.213Z" }, + { url = "https://files.pythonhosted.org/packages/f9/5c/6657823f4f594f72b5471f1db1ab12e26e890bb2e41897522d134d2a3e81/numpy-2.2.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0811bb762109d9708cca4d0b13c4f67146e3c3b7cf8d34018c722adb2d957c84", size = 20867828, upload-time = "2025-05-17T21:37:56.699Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9e/14520dc3dadf3c803473bd07e9b2bd1b69bc583cb2497b47000fed2fa92f/numpy-2.2.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:287cc3162b6f01463ccd86be154f284d0893d2b3ed7292439ea97eafa8170e0b", size = 14143006, upload-time = "2025-05-17T21:38:18.291Z" }, + { url = "https://files.pythonhosted.org/packages/4f/06/7e96c57d90bebdce9918412087fc22ca9851cceaf5567a45c1f404480e9e/numpy-2.2.6-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f1372f041402e37e5e633e586f62aa53de2eac8d98cbfb822806ce4bbefcb74d", size = 5076765, upload-time = "2025-05-17T21:38:27.319Z" }, + { url = "https://files.pythonhosted.org/packages/73/ed/63d920c23b4289fdac96ddbdd6132e9427790977d5457cd132f18e76eae0/numpy-2.2.6-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:55a4d33fa519660d69614a9fad433be87e5252f4b03850642f88993f7b2ca566", size = 6617736, upload-time = "2025-05-17T21:38:38.141Z" }, + { url = "https://files.pythonhosted.org/packages/85/c5/e19c8f99d83fd377ec8c7e0cf627a8049746da54afc24ef0a0cb73d5dfb5/numpy-2.2.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f92729c95468a2f4f15e9bb94c432a9229d0d50de67304399627a943201baa2f", size = 14010719, upload-time = "2025-05-17T21:38:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/19/49/4df9123aafa7b539317bf6d342cb6d227e49f7a35b99c287a6109b13dd93/numpy-2.2.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bc23a79bfabc5d056d106f9befb8d50c31ced2fbc70eedb8155aec74a45798f", size = 16526072, upload-time = "2025-05-17T21:39:22.638Z" }, + { url = "https://files.pythonhosted.org/packages/b2/6c/04b5f47f4f32f7c2b0e7260442a8cbcf8168b0e1a41ff1495da42f42a14f/numpy-2.2.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e3143e4451880bed956e706a3220b4e5cf6172ef05fcc397f6f36a550b1dd868", size = 15503213, upload-time = "2025-05-17T21:39:45.865Z" }, + { url = "https://files.pythonhosted.org/packages/17/0a/5cd92e352c1307640d5b6fec1b2ffb06cd0dabe7d7b8227f97933d378422/numpy-2.2.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4f13750ce79751586ae2eb824ba7e1e8dba64784086c98cdbbcc6a42112ce0d", size = 18316632, upload-time = "2025-05-17T21:40:13.331Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3b/5cba2b1d88760ef86596ad0f3d484b1cbff7c115ae2429678465057c5155/numpy-2.2.6-cp313-cp313-win32.whl", hash = "sha256:5beb72339d9d4fa36522fc63802f469b13cdbe4fdab4a288f0c441b74272ebfd", size = 6244532, upload-time = "2025-05-17T21:43:46.099Z" }, + { url = "https://files.pythonhosted.org/packages/cb/3b/d58c12eafcb298d4e6d0d40216866ab15f59e55d148a5658bb3132311fcf/numpy-2.2.6-cp313-cp313-win_amd64.whl", hash = "sha256:b0544343a702fa80c95ad5d3d608ea3599dd54d4632df855e4c8d24eb6ecfa1c", size = 12610885, upload-time = "2025-05-17T21:44:05.145Z" }, + { url = "https://files.pythonhosted.org/packages/6b/9e/4bf918b818e516322db999ac25d00c75788ddfd2d2ade4fa66f1f38097e1/numpy-2.2.6-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0bca768cd85ae743b2affdc762d617eddf3bcf8724435498a1e80132d04879e6", size = 20963467, upload-time = "2025-05-17T21:40:44Z" }, + { url = "https://files.pythonhosted.org/packages/61/66/d2de6b291507517ff2e438e13ff7b1e2cdbdb7cb40b3ed475377aece69f9/numpy-2.2.6-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fc0c5673685c508a142ca65209b4e79ed6740a4ed6b2267dbba90f34b0b3cfda", size = 14225144, upload-time = "2025-05-17T21:41:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/25/480387655407ead912e28ba3a820bc69af9adf13bcbe40b299d454ec011f/numpy-2.2.6-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:5bd4fc3ac8926b3819797a7c0e2631eb889b4118a9898c84f585a54d475b7e40", size = 5200217, upload-time = "2025-05-17T21:41:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4a/6e313b5108f53dcbf3aca0c0f3e9c92f4c10ce57a0a721851f9785872895/numpy-2.2.6-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:fee4236c876c4e8369388054d02d0e9bb84821feb1a64dd59e137e6511a551f8", size = 6712014, upload-time = "2025-05-17T21:41:27.321Z" }, + { url = "https://files.pythonhosted.org/packages/b7/30/172c2d5c4be71fdf476e9de553443cf8e25feddbe185e0bd88b096915bcc/numpy-2.2.6-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1dda9c7e08dc141e0247a5b8f49cf05984955246a327d4c48bda16821947b2f", size = 14077935, upload-time = "2025-05-17T21:41:49.738Z" }, + { url = "https://files.pythonhosted.org/packages/12/fb/9e743f8d4e4d3c710902cf87af3512082ae3d43b945d5d16563f26ec251d/numpy-2.2.6-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f447e6acb680fd307f40d3da4852208af94afdfab89cf850986c3ca00562f4fa", size = 16600122, upload-time = "2025-05-17T21:42:14.046Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/ee20da0e58d3a66f204f38916757e01e33a9737d0b22373b3eb5a27358f9/numpy-2.2.6-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:389d771b1623ec92636b0786bc4ae56abafad4a4c513d36a55dce14bd9ce8571", size = 15586143, upload-time = "2025-05-17T21:42:37.464Z" }, + { url = "https://files.pythonhosted.org/packages/76/95/bef5b37f29fc5e739947e9ce5179ad402875633308504a52d188302319c8/numpy-2.2.6-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8e9ace4a37db23421249ed236fdcdd457d671e25146786dfc96835cd951aa7c1", size = 18385260, upload-time = "2025-05-17T21:43:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/09/04/f2f83279d287407cf36a7a8053a5abe7be3622a4363337338f2585e4afda/numpy-2.2.6-cp313-cp313t-win32.whl", hash = "sha256:038613e9fb8c72b0a41f025a7e4c3f0b7a1b5d768ece4796b674c8f3fe13efff", size = 6377225, upload-time = "2025-05-17T21:43:16.254Z" }, + { url = "https://files.pythonhosted.org/packages/67/0e/35082d13c09c02c011cf21570543d202ad929d961c02a147493cb0c2bdf5/numpy-2.2.6-cp313-cp313t-win_amd64.whl", hash = "sha256:6031dd6dfecc0cf9f668681a37648373bddd6421fff6c66ec1624eed0180ee06", size = 12771374, upload-time = "2025-05-17T21:43:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/9e/3b/d94a75f4dbf1ef5d321523ecac21ef23a3cd2ac8b78ae2aac40873590229/numpy-2.2.6-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0b605b275d7bd0c640cad4e5d30fa701a8d59302e127e5f79138ad62762c3e3d", size = 21040391, upload-time = "2025-05-17T21:44:35.948Z" }, + { url = "https://files.pythonhosted.org/packages/17/f4/09b2fa1b58f0fb4f7c7963a1649c64c4d315752240377ed74d9cd878f7b5/numpy-2.2.6-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:7befc596a7dc9da8a337f79802ee8adb30a552a94f792b9c9d18c840055907db", size = 6786754, upload-time = "2025-05-17T21:44:47.446Z" }, + { url = "https://files.pythonhosted.org/packages/af/30/feba75f143bdc868a1cc3f44ccfa6c4b9ec522b36458e738cd00f67b573f/numpy-2.2.6-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce47521a4754c8f4593837384bd3424880629f718d87c5d44f8ed763edd63543", size = 16643476, upload-time = "2025-05-17T21:45:11.871Z" }, + { url = "https://files.pythonhosted.org/packages/37/48/ac2a9584402fb6c0cd5b5d1a91dcf176b15760130dd386bbafdbfe3640bf/numpy-2.2.6-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d042d24c90c41b54fd506da306759e06e568864df8ec17ccc17e9e884634fd00", size = 12812666, upload-time = "2025-05-17T21:45:31.426Z" }, +] + +[[package]] +name = "numpy" +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version == '3.11.*' and sys_platform == 'win32'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version == '3.11.*' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and sys_platform != 'emscripten' and sys_platform != 'win32'", +] +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, +] + +[[package]] +name = "openai" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/a2/677f22c4b487effb8a09439fb6134034b5f0a39ca27df8b95fac23a93720/openai-2.17.0.tar.gz", hash = "sha256:47224b74bd20f30c6b0a6a329505243cb2f26d5cf84d9f8d0825ff8b35e9c999", size = 631445, upload-time = "2026-02-05T16:27:40.953Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/97/284535aa75e6e84ab388248b5a323fc296b1f70530130dee37f7f4fbe856/openai-2.17.0-py3-none-any.whl", hash = "sha256:4f393fd886ca35e113aac7ff239bcd578b81d8f104f5aedc7d3693eb2af1d338", size = 1069524, upload-time = "2026-02-05T16:27:38.941Z" }, +] + [[package]] name = "openapi-pydantic" version = "0.5.1" @@ -1056,6 +1532,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] +[[package]] +name = "pandas" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/f7/f425a00df4fcc22b292c6895c6831c0c8ae1d9fac1e024d16f98a9ce8749/pandas-2.3.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:376c6446ae31770764215a6c937f72d917f214b43560603cd60da6408f183b6c", size = 11555763, upload-time = "2025-09-29T23:16:53.287Z" }, + { url = "https://files.pythonhosted.org/packages/13/4f/66d99628ff8ce7857aca52fed8f0066ce209f96be2fede6cef9f84e8d04f/pandas-2.3.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e19d192383eab2f4ceb30b412b22ea30690c9e618f78870357ae1d682912015a", size = 10801217, upload-time = "2025-09-29T23:17:04.522Z" }, + { url = "https://files.pythonhosted.org/packages/1d/03/3fc4a529a7710f890a239cc496fc6d50ad4a0995657dccc1d64695adb9f4/pandas-2.3.3-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5caf26f64126b6c7aec964f74266f435afef1c1b13da3b0636c7518a1fa3e2b1", size = 12148791, upload-time = "2025-09-29T23:17:18.444Z" }, + { url = "https://files.pythonhosted.org/packages/40/a8/4dac1f8f8235e5d25b9955d02ff6f29396191d4e665d71122c3722ca83c5/pandas-2.3.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd7478f1463441ae4ca7308a70e90b33470fa593429f9d4c578dd00d1fa78838", size = 12769373, upload-time = "2025-09-29T23:17:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/df/91/82cc5169b6b25440a7fc0ef3a694582418d875c8e3ebf796a6d6470aa578/pandas-2.3.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4793891684806ae50d1288c9bae9330293ab4e083ccd1c5e383c34549c6e4250", size = 13200444, upload-time = "2025-09-29T23:17:49.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/89b3283800ab58f7af2952704078555fa60c807fff764395bb57ea0b0dbd/pandas-2.3.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:28083c648d9a99a5dd035ec125d42439c6c1c525098c58af0fc38dd1a7a1b3d4", size = 13858459, upload-time = "2025-09-29T23:18:03.722Z" }, + { url = "https://files.pythonhosted.org/packages/85/72/530900610650f54a35a19476eca5104f38555afccda1aa11a92ee14cb21d/pandas-2.3.3-cp310-cp310-win_amd64.whl", hash = "sha256:503cf027cf9940d2ceaa1a93cfb5f8c8c7e6e90720a2850378f0b3f3b1e06826", size = 11346086, upload-time = "2025-09-29T23:18:18.505Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, + { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, + { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, + { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, + { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, + { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, + { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, + { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, + { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, + { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, + { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, + { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, + { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, + { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, + { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, + { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, + { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, + { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, + { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, + { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, + { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, + { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, + { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, + { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, + { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, + { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, + { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, + { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, + { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, + { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, + { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, + { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, + { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, +] + [[package]] name = "pathable" version = "0.4.4" @@ -1074,6 +1612,104 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, ] +[[package]] +name = "pillow" +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fe/41/f73d92b6b883a579e79600d391f2e21cb0df767b2714ecbd2952315dfeef/pillow-12.1.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:fb125d860738a09d363a88daa0f59c4533529a90e564785e20fe875b200b6dbd", size = 5304089, upload-time = "2026-01-02T09:10:24.953Z" }, + { url = "https://files.pythonhosted.org/packages/94/55/7aca2891560188656e4a91ed9adba305e914a4496800da6b5c0a15f09edf/pillow-12.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cad302dc10fac357d3467a74a9561c90609768a6f73a1923b0fd851b6486f8b0", size = 4657815, upload-time = "2026-01-02T09:10:27.063Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d2/b28221abaa7b4c40b7dba948f0f6a708bd7342c4d47ce342f0ea39643974/pillow-12.1.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a40905599d8079e09f25027423aed94f2823adaf2868940de991e53a449e14a8", size = 6222593, upload-time = "2026-01-02T09:10:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/71/b8/7a61fb234df6a9b0b479f69e66901209d89ff72a435b49933f9122f94cac/pillow-12.1.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:92a7fe4225365c5e3a8e598982269c6d6698d3e783b3b1ae979e7819f9cd55c1", size = 8027579, upload-time = "2026-01-02T09:10:31.182Z" }, + { url = "https://files.pythonhosted.org/packages/ea/51/55c751a57cc524a15a0e3db20e5cde517582359508d62305a627e77fd295/pillow-12.1.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f10c98f49227ed8383d28174ee95155a675c4ed7f85e2e573b04414f7e371bda", size = 6335760, upload-time = "2026-01-02T09:10:33.02Z" }, + { url = "https://files.pythonhosted.org/packages/dc/7c/60e3e6f5e5891a1a06b4c910f742ac862377a6fe842f7184df4a274ce7bf/pillow-12.1.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8637e29d13f478bc4f153d8daa9ffb16455f0a6cb287da1b432fdad2bfbd66c7", size = 7027127, upload-time = "2026-01-02T09:10:35.009Z" }, + { url = "https://files.pythonhosted.org/packages/06/37/49d47266ba50b00c27ba63a7c898f1bb41a29627ced8c09e25f19ebec0ff/pillow-12.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:21e686a21078b0f9cb8c8a961d99e6a4ddb88e0fc5ea6e130172ddddc2e5221a", size = 6449896, upload-time = "2026-01-02T09:10:36.793Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/67fd87d2913902462cd9b79c6211c25bfe95fcf5783d06e1367d6d9a741f/pillow-12.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2415373395a831f53933c23ce051021e79c8cd7979822d8cc478547a3f4da8ef", size = 7151345, upload-time = "2026-01-02T09:10:39.064Z" }, + { url = "https://files.pythonhosted.org/packages/bd/15/f8c7abf82af68b29f50d77c227e7a1f87ce02fdc66ded9bf603bc3b41180/pillow-12.1.0-cp310-cp310-win32.whl", hash = "sha256:e75d3dba8fc1ddfec0cd752108f93b83b4f8d6ab40e524a95d35f016b9683b09", size = 6325568, upload-time = "2026-01-02T09:10:41.035Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/7d1c0e160b6b5ac2605ef7d8be537e28753c0db5363d035948073f5513d7/pillow-12.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:64efdf00c09e31efd754448a383ea241f55a994fd079866b92d2bbff598aad91", size = 7032367, upload-time = "2026-01-02T09:10:43.09Z" }, + { url = "https://files.pythonhosted.org/packages/f4/03/41c038f0d7a06099254c60f618d0ec7be11e79620fc23b8e85e5b31d9a44/pillow-12.1.0-cp310-cp310-win_arm64.whl", hash = "sha256:f188028b5af6b8fb2e9a76ac0f841a575bd1bd396e46ef0840d9b88a48fdbcea", size = 2452345, upload-time = "2026-01-02T09:10:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c7/2530a4aa28248623e9d7f27316b42e27c32ec410f695929696f2e0e4a778/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:7b5dd7cbae20285cdb597b10eb5a2c13aa9de6cde9bb64a3c1317427b1db1ae1", size = 4062543, upload-time = "2026-01-02T09:11:31.566Z" }, + { url = "https://files.pythonhosted.org/packages/8f/1f/40b8eae823dc1519b87d53c30ed9ef085506b05281d313031755c1705f73/pillow-12.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:29a4cef9cb672363926f0470afc516dbf7305a14d8c54f7abbb5c199cd8f8179", size = 4138373, upload-time = "2026-01-02T09:11:33.367Z" }, + { url = "https://files.pythonhosted.org/packages/d4/77/6fa60634cf06e52139fd0e89e5bbf055e8166c691c42fb162818b7fda31d/pillow-12.1.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:681088909d7e8fa9e31b9799aaa59ba5234c58e5e4f1951b4c4d1082a2e980e0", size = 3601241, upload-time = "2026-01-02T09:11:35.011Z" }, + { url = "https://files.pythonhosted.org/packages/4f/bf/28ab865de622e14b747f0cd7877510848252d950e43002e224fb1c9ababf/pillow-12.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:983976c2ab753166dc66d36af6e8ec15bb511e4a25856e2227e5f7e00a160587", size = 5262410, upload-time = "2026-01-02T09:11:36.682Z" }, + { url = "https://files.pythonhosted.org/packages/1c/34/583420a1b55e715937a85bd48c5c0991598247a1fd2eb5423188e765ea02/pillow-12.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:db44d5c160a90df2d24a24760bbd37607d53da0b34fb546c4c232af7192298ac", size = 4657312, upload-time = "2026-01-02T09:11:38.535Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fd/f5a0896839762885b3376ff04878f86ab2b097c2f9a9cdccf4eda8ba8dc0/pillow-12.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6b7a9d1db5dad90e2991645874f708e87d9a3c370c243c2d7684d28f7e133e6b", size = 6232605, upload-time = "2026-01-02T09:11:40.602Z" }, + { url = "https://files.pythonhosted.org/packages/98/aa/938a09d127ac1e70e6ed467bd03834350b33ef646b31edb7452d5de43792/pillow-12.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6258f3260986990ba2fa8a874f8b6e808cf5abb51a94015ca3dc3c68aa4f30ea", size = 8041617, upload-time = "2026-01-02T09:11:42.721Z" }, + { url = "https://files.pythonhosted.org/packages/17/e8/538b24cb426ac0186e03f80f78bc8dc7246c667f58b540bdd57c71c9f79d/pillow-12.1.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e115c15e3bc727b1ca3e641a909f77f8ca72a64fff150f666fcc85e57701c26c", size = 6346509, upload-time = "2026-01-02T09:11:44.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/9a/632e58ec89a32738cabfd9ec418f0e9898a2b4719afc581f07c04a05e3c9/pillow-12.1.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6741e6f3074a35e47c77b23a4e4f2d90db3ed905cb1c5e6e0d49bff2045632bc", size = 7038117, upload-time = "2026-01-02T09:11:46.736Z" }, + { url = "https://files.pythonhosted.org/packages/c7/a2/d40308cf86eada842ca1f3ffa45d0ca0df7e4ab33c83f81e73f5eaed136d/pillow-12.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:935b9d1aed48fcfb3f838caac506f38e29621b44ccc4f8a64d575cb1b2a88644", size = 6460151, upload-time = "2026-01-02T09:11:48.625Z" }, + { url = "https://files.pythonhosted.org/packages/f1/88/f5b058ad6453a085c5266660a1417bdad590199da1b32fb4efcff9d33b05/pillow-12.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5fee4c04aad8932da9f8f710af2c1a15a83582cfb884152a9caa79d4efcdbf9c", size = 7164534, upload-time = "2026-01-02T09:11:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/19/ce/c17334caea1db789163b5d855a5735e47995b0b5dc8745e9a3605d5f24c0/pillow-12.1.0-cp313-cp313-win32.whl", hash = "sha256:a786bf667724d84aa29b5db1c61b7bfdde380202aaca12c3461afd6b71743171", size = 6332551, upload-time = "2026-01-02T09:11:52.234Z" }, + { url = "https://files.pythonhosted.org/packages/e5/07/74a9d941fa45c90a0d9465098fe1ec85de3e2afbdc15cc4766622d516056/pillow-12.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:461f9dfdafa394c59cd6d818bdfdbab4028b83b02caadaff0ffd433faf4c9a7a", size = 7040087, upload-time = "2026-01-02T09:11:54.822Z" }, + { url = "https://files.pythonhosted.org/packages/88/09/c99950c075a0e9053d8e880595926302575bc742b1b47fe1bbcc8d388d50/pillow-12.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:9212d6b86917a2300669511ed094a9406888362e085f2431a7da985a6b124f45", size = 2452470, upload-time = "2026-01-02T09:11:56.522Z" }, + { url = "https://files.pythonhosted.org/packages/b5/ba/970b7d85ba01f348dee4d65412476321d40ee04dcb51cd3735b9dc94eb58/pillow-12.1.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:00162e9ca6d22b7c3ee8e61faa3c3253cd19b6a37f126cad04f2f88b306f557d", size = 5264816, upload-time = "2026-01-02T09:11:58.227Z" }, + { url = "https://files.pythonhosted.org/packages/10/60/650f2fb55fdba7a510d836202aa52f0baac633e50ab1cf18415d332188fb/pillow-12.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7d6daa89a00b58c37cb1747ec9fb7ac3bc5ffd5949f5888657dfddde6d1312e0", size = 4660472, upload-time = "2026-01-02T09:12:00.798Z" }, + { url = "https://files.pythonhosted.org/packages/2b/c0/5273a99478956a099d533c4f46cbaa19fd69d606624f4334b85e50987a08/pillow-12.1.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2479c7f02f9d505682dc47df8c0ea1fc5e264c4d1629a5d63fe3e2334b89554", size = 6268974, upload-time = "2026-01-02T09:12:02.572Z" }, + { url = "https://files.pythonhosted.org/packages/b4/26/0bf714bc2e73d5267887d47931d53c4ceeceea6978148ed2ab2a4e6463c4/pillow-12.1.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f188d580bd870cda1e15183790d1cc2fa78f666e76077d103edf048eed9c356e", size = 8073070, upload-time = "2026-01-02T09:12:04.75Z" }, + { url = "https://files.pythonhosted.org/packages/43/cf/1ea826200de111a9d65724c54f927f3111dc5ae297f294b370a670c17786/pillow-12.1.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fde7ec5538ab5095cc02df38ee99b0443ff0e1c847a045554cf5f9af1f4aa82", size = 6380176, upload-time = "2026-01-02T09:12:06.626Z" }, + { url = "https://files.pythonhosted.org/packages/03/e0/7938dd2b2013373fd85d96e0f38d62b7a5a262af21ac274250c7ca7847c9/pillow-12.1.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0ed07dca4a8464bada6139ab38f5382f83e5f111698caf3191cb8dbf27d908b4", size = 7067061, upload-time = "2026-01-02T09:12:08.624Z" }, + { url = "https://files.pythonhosted.org/packages/86/ad/a2aa97d37272a929a98437a8c0ac37b3cf012f4f8721e1bd5154699b2518/pillow-12.1.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f45bd71d1fa5e5749587613037b172e0b3b23159d1c00ef2fc920da6f470e6f0", size = 6491824, upload-time = "2026-01-02T09:12:10.488Z" }, + { url = "https://files.pythonhosted.org/packages/a4/44/80e46611b288d51b115826f136fb3465653c28f491068a72d3da49b54cd4/pillow-12.1.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:277518bf4fe74aa91489e1b20577473b19ee70fb97c374aa50830b279f25841b", size = 7190911, upload-time = "2026-01-02T09:12:12.772Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/eacc62356b4cf81abe99ff9dbc7402750044aed02cfd6a503f7c6fc11f3e/pillow-12.1.0-cp313-cp313t-win32.whl", hash = "sha256:7315f9137087c4e0ee73a761b163fc9aa3b19f5f606a7fc08d83fd3e4379af65", size = 6336445, upload-time = "2026-01-02T09:12:14.775Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3c/57d81d0b74d218706dafccb87a87ea44262c43eef98eb3b164fd000e0491/pillow-12.1.0-cp313-cp313t-win_amd64.whl", hash = "sha256:0ddedfaa8b5f0b4ffbc2fa87b556dc59f6bb4ecb14a53b33f9189713ae8053c0", size = 7045354, upload-time = "2026-01-02T09:12:16.599Z" }, + { url = "https://files.pythonhosted.org/packages/ac/82/8b9b97bba2e3576a340f93b044a3a3a09841170ab4c1eb0d5c93469fd32f/pillow-12.1.0-cp313-cp313t-win_arm64.whl", hash = "sha256:80941e6d573197a0c28f394753de529bb436b1ca990ed6e765cf42426abc39f8", size = 2454547, upload-time = "2026-01-02T09:12:18.704Z" }, + { url = "https://files.pythonhosted.org/packages/8c/87/bdf971d8bbcf80a348cc3bacfcb239f5882100fe80534b0ce67a784181d8/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:5cb7bc1966d031aec37ddb9dcf15c2da5b2e9f7cc3ca7c54473a20a927e1eb91", size = 4062533, upload-time = "2026-01-02T09:12:20.791Z" }, + { url = "https://files.pythonhosted.org/packages/ff/4f/5eb37a681c68d605eb7034c004875c81f86ec9ef51f5be4a63eadd58859a/pillow-12.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:97e9993d5ed946aba26baf9c1e8cf18adbab584b99f452ee72f7ee8acb882796", size = 4138546, upload-time = "2026-01-02T09:12:23.664Z" }, + { url = "https://files.pythonhosted.org/packages/11/6d/19a95acb2edbace40dcd582d077b991646b7083c41b98da4ed7555b59733/pillow-12.1.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:414b9a78e14ffeb98128863314e62c3f24b8a86081066625700b7985b3f529bd", size = 3601163, upload-time = "2026-01-02T09:12:26.338Z" }, + { url = "https://files.pythonhosted.org/packages/fc/36/2b8138e51cb42e4cc39c3297713455548be855a50558c3ac2beebdc251dd/pillow-12.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e6bdb408f7c9dd2a5ff2b14a3b0bb6d4deb29fb9961e6eb3ae2031ae9a5cec13", size = 5266086, upload-time = "2026-01-02T09:12:28.782Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/649056e4d22e1caa90816bf99cef0884aed607ed38075bd75f091a607a38/pillow-12.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3413c2ae377550f5487991d444428f1a8ae92784aac79caa8b1e3b89b175f77e", size = 4657344, upload-time = "2026-01-02T09:12:31.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/6b/c5742cea0f1ade0cd61485dc3d81f05261fc2276f537fbdc00802de56779/pillow-12.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e5dcbe95016e88437ecf33544ba5db21ef1b8dd6e1b434a2cb2a3d605299e643", size = 6232114, upload-time = "2026-01-02T09:12:32.936Z" }, + { url = "https://files.pythonhosted.org/packages/bf/8f/9f521268ce22d63991601aafd3d48d5ff7280a246a1ef62d626d67b44064/pillow-12.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d0a7735df32ccbcc98b98a1ac785cc4b19b580be1bdf0aeb5c03223220ea09d5", size = 8042708, upload-time = "2026-01-02T09:12:34.78Z" }, + { url = "https://files.pythonhosted.org/packages/1a/eb/257f38542893f021502a1bbe0c2e883c90b5cff26cc33b1584a841a06d30/pillow-12.1.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c27407a2d1b96774cbc4a7594129cc027339fd800cd081e44497722ea1179de", size = 6347762, upload-time = "2026-01-02T09:12:36.748Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8ba375025701c09b309e8d5163c5a4ce0102fa86bbf8800eb0d7ac87bc51/pillow-12.1.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15c794d74303828eaa957ff8070846d0efe8c630901a1c753fdc63850e19ecd9", size = 7039265, upload-time = "2026-01-02T09:12:39.082Z" }, + { url = "https://files.pythonhosted.org/packages/cf/dc/cf5e4cdb3db533f539e88a7bbf9f190c64ab8a08a9bc7a4ccf55067872e4/pillow-12.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c990547452ee2800d8506c4150280757f88532f3de2a58e3022e9b179107862a", size = 6462341, upload-time = "2026-01-02T09:12:40.946Z" }, + { url = "https://files.pythonhosted.org/packages/d0/47/0291a25ac9550677e22eda48510cfc4fa4b2ef0396448b7fbdc0a6946309/pillow-12.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b63e13dd27da389ed9475b3d28510f0f954bca0041e8e551b2a4eb1eab56a39a", size = 7165395, upload-time = "2026-01-02T09:12:42.706Z" }, + { url = "https://files.pythonhosted.org/packages/4f/4c/e005a59393ec4d9416be06e6b45820403bb946a778e39ecec62f5b2b991e/pillow-12.1.0-cp314-cp314-win32.whl", hash = "sha256:1a949604f73eb07a8adab38c4fe50791f9919344398bdc8ac6b307f755fc7030", size = 6431413, upload-time = "2026-01-02T09:12:44.944Z" }, + { url = "https://files.pythonhosted.org/packages/1c/af/f23697f587ac5f9095d67e31b81c95c0249cd461a9798a061ed6709b09b5/pillow-12.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:4f9f6a650743f0ddee5593ac9e954ba1bdbc5e150bc066586d4f26127853ab94", size = 7176779, upload-time = "2026-01-02T09:12:46.727Z" }, + { url = "https://files.pythonhosted.org/packages/b3/36/6a51abf8599232f3e9afbd16d52829376a68909fe14efe29084445db4b73/pillow-12.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:808b99604f7873c800c4840f55ff389936ef1948e4e87645eaf3fccbc8477ac4", size = 2543105, upload-time = "2026-01-02T09:12:49.243Z" }, + { url = "https://files.pythonhosted.org/packages/82/54/2e1dd20c8749ff225080d6ba465a0cab4387f5db0d1c5fb1439e2d99923f/pillow-12.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc11908616c8a283cf7d664f77411a5ed2a02009b0097ff8abbba5e79128ccf2", size = 5268571, upload-time = "2026-01-02T09:12:51.11Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/571163a5ef86ec0cf30d265ac2a70ae6fc9e28413d1dc94fa37fae6bda89/pillow-12.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:896866d2d436563fa2a43a9d72f417874f16b5545955c54a64941e87c1376c61", size = 4660426, upload-time = "2026-01-02T09:12:52.865Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e1/53ee5163f794aef1bf84243f755ee6897a92c708505350dd1923f4afec48/pillow-12.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8e178e3e99d3c0ea8fc64b88447f7cac8ccf058af422a6cedc690d0eadd98c51", size = 6269908, upload-time = "2026-01-02T09:12:54.884Z" }, + { url = "https://files.pythonhosted.org/packages/bc/0b/b4b4106ff0ee1afa1dc599fde6ab230417f800279745124f6c50bcffed8e/pillow-12.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:079af2fb0c599c2ec144ba2c02766d1b55498e373b3ac64687e43849fbbef5bc", size = 8074733, upload-time = "2026-01-02T09:12:56.802Z" }, + { url = "https://files.pythonhosted.org/packages/19/9f/80b411cbac4a732439e629a26ad3ef11907a8c7fc5377b7602f04f6fe4e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdec5e43377761c5dbca620efb69a77f6855c5a379e32ac5b158f54c84212b14", size = 6381431, upload-time = "2026-01-02T09:12:58.823Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b7/d65c45db463b66ecb6abc17c6ba6917a911202a07662247e1355ce1789e7/pillow-12.1.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:565c986f4b45c020f5421a4cea13ef294dde9509a8577f29b2fc5edc7587fff8", size = 7068529, upload-time = "2026-01-02T09:13:00.885Z" }, + { url = "https://files.pythonhosted.org/packages/50/96/dfd4cd726b4a45ae6e3c669fc9e49deb2241312605d33aba50499e9d9bd1/pillow-12.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:43aca0a55ce1eefc0aefa6253661cb54571857b1a7b2964bd8a1e3ef4b729924", size = 6492981, upload-time = "2026-01-02T09:13:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/4d/1c/b5dc52cf713ae46033359c5ca920444f18a6359ce1020dd3e9c553ea5bc6/pillow-12.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0deedf2ea233722476b3a81e8cdfbad786f7adbed5d848469fa59fe52396e4ef", size = 7191878, upload-time = "2026-01-02T09:13:05.276Z" }, + { url = "https://files.pythonhosted.org/packages/53/26/c4188248bd5edaf543864fe4834aebe9c9cb4968b6f573ce014cc42d0720/pillow-12.1.0-cp314-cp314t-win32.whl", hash = "sha256:b17fbdbe01c196e7e159aacb889e091f28e61020a8abeac07b68079b6e626988", size = 6438703, upload-time = "2026-01-02T09:13:07.491Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0e/69ed296de8ea05cb03ee139cee600f424ca166e632567b2d66727f08c7ed/pillow-12.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27b9baecb428899db6c0de572d6d305cfaf38ca1596b5c0542a5182e3e74e8c6", size = 7182927, upload-time = "2026-01-02T09:13:09.841Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f5/68334c015eed9b5cff77814258717dec591ded209ab5b6fb70e2ae873d1d/pillow-12.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:f61333d817698bdcdd0f9d7793e365ac3d2a21c1f1eb02b32ad6aefb8d8ea831", size = 2545104, upload-time = "2026-01-02T09:13:12.068Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, +] + [[package]] name = "platformdirs" version = "4.5.1" @@ -1101,6 +1737,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" }, ] +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + [[package]] name = "py-key-value-aio" version = "0.3.0" @@ -1142,6 +1793,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/51/e4/b8b0a03ece72f47dce2307d36e1c34725b7223d209fc679315ffe6a4e2c3/py_key_value_shared-0.3.0-py3-none-any.whl", hash = "sha256:5b0efba7ebca08bb158b1e93afc2f07d30b8f40c2fc12ce24a4c0d84f42f9298", size = 19560, upload-time = "2025-11-17T16:50:05.954Z" }, ] +[[package]] +name = "pyarrow" +version = "23.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/33/ffd9c3eb087fa41dd79c3cf20c4c0ae3cdb877c4f8e1107a446006344924/pyarrow-23.0.0.tar.gz", hash = "sha256:180e3150e7edfcd182d3d9afba72f7cf19839a497cc76555a8dce998a8f67615", size = 1167185, upload-time = "2026-01-18T16:19:42.218Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/2f/23e042a5aa99bcb15e794e14030e8d065e00827e846e53a66faec73c7cd6/pyarrow-23.0.0-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:cbdc2bf5947aa4d462adcf8453cf04aee2f7932653cb67a27acd96e5e8528a67", size = 34281861, upload-time = "2026-01-18T16:13:34.332Z" }, + { url = "https://files.pythonhosted.org/packages/8b/65/1651933f504b335ec9cd8f99463718421eb08d883ed84f0abd2835a16cad/pyarrow-23.0.0-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:4d38c836930ce15cd31dce20114b21ba082da231c884bdc0a7b53e1477fe7f07", size = 35825067, upload-time = "2026-01-18T16:13:42.549Z" }, + { url = "https://files.pythonhosted.org/packages/84/ec/d6fceaec050c893f4e35c0556b77d4cc9973fcc24b0a358a5781b1234582/pyarrow-23.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:4222ff8f76919ecf6c716175a0e5fddb5599faeed4c56d9ea41a2c42be4998b2", size = 44458539, upload-time = "2026-01-18T16:13:52.975Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/369f134d652b21db62fe3ec1c5c2357e695f79eb67394b8a93f3a2b2cffa/pyarrow-23.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:87f06159cbe38125852657716889296c83c37b4d09a5e58f3d10245fd1f69795", size = 47535889, upload-time = "2026-01-18T16:14:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/a3/95/f37b6a252fdbf247a67a78fb3f61a529fe0600e304c4d07741763d3522b1/pyarrow-23.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:1675c374570d8b91ea6d4edd4608fa55951acd44e0c31bd146e091b4005de24f", size = 48157777, upload-time = "2026-01-18T16:14:12.483Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ab/fb94923108c9c6415dab677cf1f066d3307798eafc03f9a65ab4abc61056/pyarrow-23.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:247374428fde4f668f138b04031a7e7077ba5fa0b5b1722fdf89a017bf0b7ee0", size = 50580441, upload-time = "2026-01-18T16:14:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/ae/78/897ba6337b517fc8e914891e1bd918da1c4eb8e936a553e95862e67b80f6/pyarrow-23.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:de53b1bd3b88a2ee93c9af412c903e57e738c083be4f6392288294513cd8b2c1", size = 27530028, upload-time = "2026-01-18T16:14:27.353Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c0/57fe251102ca834fee0ef69a84ad33cc0ff9d5dfc50f50b466846356ecd7/pyarrow-23.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:5574d541923efcbfdf1294a2746ae3b8c2498a2dc6cd477882f6f4e7b1ac08d3", size = 34276762, upload-time = "2026-01-18T16:14:34.128Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4e/24130286548a5bc250cbed0b6bbf289a2775378a6e0e6f086ae8c68fc098/pyarrow-23.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:2ef0075c2488932e9d3c2eb3482f9459c4be629aa673b725d5e3cf18f777f8e4", size = 35821420, upload-time = "2026-01-18T16:14:40.699Z" }, + { url = "https://files.pythonhosted.org/packages/ee/55/a869e8529d487aa2e842d6c8865eb1e2c9ec33ce2786eb91104d2c3e3f10/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:65666fc269669af1ef1c14478c52222a2aa5c907f28b68fb50a203c777e4f60c", size = 44457412, upload-time = "2026-01-18T16:14:49.051Z" }, + { url = "https://files.pythonhosted.org/packages/36/81/1de4f0edfa9a483bbdf0082a05790bd6a20ed2169ea12a65039753be3a01/pyarrow-23.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:4d85cb6177198f3812db4788e394b757223f60d9a9f5ad6634b3e32be1525803", size = 47534285, upload-time = "2026-01-18T16:14:56.748Z" }, + { url = "https://files.pythonhosted.org/packages/f2/04/464a052d673b5ece074518f27377861662449f3c1fdb39ce740d646fd098/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1a9ff6fa4141c24a03a1a434c63c8fa97ce70f8f36bccabc18ebba905ddf0f17", size = 48157913, upload-time = "2026-01-18T16:15:05.114Z" }, + { url = "https://files.pythonhosted.org/packages/f4/1b/32a4de9856ee6688c670ca2def588382e573cce45241a965af04c2f61687/pyarrow-23.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:84839d060a54ae734eb60a756aeacb62885244aaa282f3c968f5972ecc7b1ecc", size = 50582529, upload-time = "2026-01-18T16:15:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/db/c7/d6581f03e9b9e44ea60b52d1750ee1a7678c484c06f939f45365a45f7eef/pyarrow-23.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:a149a647dbfe928ce8830a713612aa0b16e22c64feac9d1761529778e4d4eaa5", size = 27542646, upload-time = "2026-01-18T16:15:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/3d/bd/c861d020831ee57609b73ea721a617985ece817684dc82415b0bc3e03ac3/pyarrow-23.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:5961a9f646c232697c24f54d3419e69b4261ba8a8b66b0ac54a1851faffcbab8", size = 34189116, upload-time = "2026-01-18T16:15:28.054Z" }, + { url = "https://files.pythonhosted.org/packages/8c/23/7725ad6cdcbaf6346221391e7b3eecd113684c805b0a95f32014e6fa0736/pyarrow-23.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:632b3e7c3d232f41d64e1a4a043fb82d44f8a349f339a1188c6a0dd9d2d47d8a", size = 35803831, upload-time = "2026-01-18T16:15:33.798Z" }, + { url = "https://files.pythonhosted.org/packages/57/06/684a421543455cdc2944d6a0c2cc3425b028a4c6b90e34b35580c4899743/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:76242c846db1411f1d6c2cc3823be6b86b40567ee24493344f8226ba34a81333", size = 44436452, upload-time = "2026-01-18T16:15:41.598Z" }, + { url = "https://files.pythonhosted.org/packages/c6/6f/8f9eb40c2328d66e8b097777ddcf38494115ff9f1b5bc9754ba46991191e/pyarrow-23.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b73519f8b52ae28127000986bf228fda781e81d3095cd2d3ece76eb5cf760e1b", size = 47557396, upload-time = "2026-01-18T16:15:51.252Z" }, + { url = "https://files.pythonhosted.org/packages/10/6e/f08075f1472e5159553501fde2cc7bc6700944bdabe49a03f8a035ee6ccd/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:068701f6823449b1b6469120f399a1239766b117d211c5d2519d4ed5861f75de", size = 48147129, upload-time = "2026-01-18T16:16:00.299Z" }, + { url = "https://files.pythonhosted.org/packages/7d/82/d5a680cd507deed62d141cc7f07f7944a6766fc51019f7f118e4d8ad0fb8/pyarrow-23.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1801ba947015d10e23bca9dd6ef5d0e9064a81569a89b6e9a63b59224fd060df", size = 50596642, upload-time = "2026-01-18T16:16:08.502Z" }, + { url = "https://files.pythonhosted.org/packages/a9/26/4f29c61b3dce9fa7780303b86895ec6a0917c9af927101daaaf118fbe462/pyarrow-23.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:52265266201ec25b6839bf6bd4ea918ca6d50f31d13e1cf200b4261cd11dc25c", size = 27660628, upload-time = "2026-01-18T16:16:15.28Z" }, + { url = "https://files.pythonhosted.org/packages/66/34/564db447d083ec7ff93e0a883a597d2f214e552823bfc178a2d0b1f2c257/pyarrow-23.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:ad96a597547af7827342ffb3c503c8316e5043bb09b47a84885ce39394c96e00", size = 34184630, upload-time = "2026-01-18T16:16:22.141Z" }, + { url = "https://files.pythonhosted.org/packages/aa/3a/3999daebcb5e6119690c92a621c4d78eef2ffba7a0a1b56386d2875fcd77/pyarrow-23.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:b9edf990df77c2901e79608f08c13fbde60202334a4fcadb15c1f57bf7afee43", size = 35796820, upload-time = "2026-01-18T16:16:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/39195233056c6a8d0976d7d1ac1cd4fe21fb0ec534eca76bc23ef3f60e11/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:36d1b5bc6ddcaff0083ceec7e2561ed61a51f49cce8be079ee8ed406acb6fdef", size = 44438735, upload-time = "2026-01-18T16:16:38.79Z" }, + { url = "https://files.pythonhosted.org/packages/2c/41/6a7328ee493527e7afc0c88d105ecca69a3580e29f2faaeac29308369fd7/pyarrow-23.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4292b889cd224f403304ddda8b63a36e60f92911f89927ec8d98021845ea21be", size = 47557263, upload-time = "2026-01-18T16:16:46.248Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ee/34e95b21ee84db494eae60083ddb4383477b31fb1fd19fd866d794881696/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dfd9e133e60eaa847fd80530a1b89a052f09f695d0b9c34c235ea6b2e0924cf7", size = 48153529, upload-time = "2026-01-18T16:16:53.412Z" }, + { url = "https://files.pythonhosted.org/packages/52/88/8a8d83cea30f4563efa1b7bf51d241331ee5cd1b185a7e063f5634eca415/pyarrow-23.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:832141cc09fac6aab1cd3719951d23301396968de87080c57c9a7634e0ecd068", size = 50598851, upload-time = "2026-01-18T16:17:01.133Z" }, + { url = "https://files.pythonhosted.org/packages/c6/4c/2929c4be88723ba025e7b3453047dc67e491c9422965c141d24bab6b5962/pyarrow-23.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:7a7d067c9a88faca655c71bcc30ee2782038d59c802d57950826a07f60d83c4c", size = 27577747, upload-time = "2026-01-18T16:18:02.413Z" }, + { url = "https://files.pythonhosted.org/packages/64/52/564a61b0b82d72bd68ec3aef1adda1e3eba776f89134b9ebcb5af4b13cb6/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:ce9486e0535a843cf85d990e2ec5820a47918235183a5c7b8b97ed7e92c2d47d", size = 34446038, upload-time = "2026-01-18T16:17:07.861Z" }, + { url = "https://files.pythonhosted.org/packages/cc/c9/232d4f9855fd1de0067c8a7808a363230d223c83aeee75e0fe6eab851ba9/pyarrow-23.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:075c29aeaa685fd1182992a9ed2499c66f084ee54eea47da3eb76e125e06064c", size = 35921142, upload-time = "2026-01-18T16:17:15.401Z" }, + { url = "https://files.pythonhosted.org/packages/96/f2/60af606a3748367b906bb82d41f0032e059f075444445d47e32a7ff1df62/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:799965a5379589510d888be3094c2296efd186a17ca1cef5b77703d4d5121f53", size = 44490374, upload-time = "2026-01-18T16:17:23.93Z" }, + { url = "https://files.pythonhosted.org/packages/ff/2d/7731543050a678ea3a413955a2d5d80d2a642f270aa57a3cb7d5a86e3f46/pyarrow-23.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ef7cac8fe6fccd8b9e7617bfac785b0371a7fe26af59463074e4882747145d40", size = 47527896, upload-time = "2026-01-18T16:17:33.393Z" }, + { url = "https://files.pythonhosted.org/packages/5a/90/f3342553b7ac9879413aed46500f1637296f3c8222107523a43a1c08b42a/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15a414f710dc927132dd67c361f78c194447479555af57317066ee5116b90e9e", size = 48210401, upload-time = "2026-01-18T16:17:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/f3/da/9862ade205ecc46c172b6ce5038a74b5151c7401e36255f15975a45878b2/pyarrow-23.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e0d2e6915eca7d786be6a77bf227fbc06d825a75b5b5fe9bcbef121dec32685", size = 50579677, upload-time = "2026-01-18T16:17:50.241Z" }, + { url = "https://files.pythonhosted.org/packages/c2/4c/f11f371f5d4740a5dafc2e11c76bcf42d03dfdb2d68696da97de420b6963/pyarrow-23.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:4b317ea6e800b5704e5e5929acb6e2dc13e9276b708ea97a39eb8b345aa2658b", size = 27631889, upload-time = "2026-01-18T16:17:56.55Z" }, + { url = "https://files.pythonhosted.org/packages/97/bb/15aec78bcf43a0c004067bd33eb5352836a29a49db8581fc56f2b6ca88b7/pyarrow-23.0.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:20b187ed9550d233a872074159f765f52f9d92973191cd4b93f293a19efbe377", size = 34213265, upload-time = "2026-01-18T16:18:07.904Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/deb2c594bbba41c37c5d9aa82f510376998352aa69dfcb886cb4b18ad80f/pyarrow-23.0.0-cp314-cp314-macosx_12_0_x86_64.whl", hash = "sha256:18ec84e839b493c3886b9b5e06861962ab4adfaeb79b81c76afbd8d84c7d5fda", size = 35819211, upload-time = "2026-01-18T16:18:13.94Z" }, + { url = "https://files.pythonhosted.org/packages/e0/e5/ee82af693cb7b5b2b74f6524cdfede0e6ace779d7720ebca24d68b57c36b/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e438dd3f33894e34fd02b26bd12a32d30d006f5852315f611aa4add6c7fab4bc", size = 44502313, upload-time = "2026-01-18T16:18:20.367Z" }, + { url = "https://files.pythonhosted.org/packages/9c/86/95c61ad82236495f3c31987e85135926ba3ec7f3819296b70a68d8066b49/pyarrow-23.0.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:a244279f240c81f135631be91146d7fa0e9e840e1dfed2aba8483eba25cd98e6", size = 47585886, upload-time = "2026-01-18T16:18:27.544Z" }, + { url = "https://files.pythonhosted.org/packages/bb/6e/a72d901f305201802f016d015de1e05def7706fff68a1dedefef5dc7eff7/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c4692e83e42438dba512a570c6eaa42be2f8b6c0f492aea27dec54bdc495103a", size = 48207055, upload-time = "2026-01-18T16:18:35.425Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/5de029c537630ca18828db45c30e2a78da03675a70ac6c3528203c416fe3/pyarrow-23.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:ae7f30f898dfe44ea69654a35c93e8da4cef6606dc4c72394068fd95f8e9f54a", size = 50619812, upload-time = "2026-01-18T16:18:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/59/8d/2af846cd2412e67a087f5bda4a8e23dfd4ebd570f777db2e8686615dafc1/pyarrow-23.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:5b86bb649e4112fb0614294b7d0a175c7513738876b89655605ebb87c804f861", size = 28263851, upload-time = "2026-01-18T16:19:38.567Z" }, + { url = "https://files.pythonhosted.org/packages/7b/7f/caab863e587041156f6786c52e64151b7386742c8c27140f637176e9230e/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:ebc017d765d71d80a3f8584ca0566b53e40464586585ac64176115baa0ada7d3", size = 34463240, upload-time = "2026-01-18T16:18:49.755Z" }, + { url = "https://files.pythonhosted.org/packages/c9/fa/3a5b8c86c958e83622b40865e11af0857c48ec763c11d472c87cd518283d/pyarrow-23.0.0-cp314-cp314t-macosx_12_0_x86_64.whl", hash = "sha256:0800cc58a6d17d159df823f87ad66cefebf105b982493d4bad03ee7fab84b993", size = 35935712, upload-time = "2026-01-18T16:18:55.626Z" }, + { url = "https://files.pythonhosted.org/packages/c5/08/17a62078fc1a53decb34a9aa79cf9009efc74d63d2422e5ade9fed2f99e3/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:3a7c68c722da9bb5b0f8c10e3eae71d9825a4b429b40b32709df5d1fa55beb3d", size = 44503523, upload-time = "2026-01-18T16:19:03.958Z" }, + { url = "https://files.pythonhosted.org/packages/cc/70/84d45c74341e798aae0323d33b7c39194e23b1abc439ceaf60a68a7a969a/pyarrow-23.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:bd5556c24622df90551063ea41f559b714aa63ca953db884cfb958559087a14e", size = 47542490, upload-time = "2026-01-18T16:19:11.208Z" }, + { url = "https://files.pythonhosted.org/packages/61/d9/d1274b0e6f19e235de17441e53224f4716574b2ca837022d55702f24d71d/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54810f6e6afc4ffee7c2e0051b61722fbea9a4961b46192dcfae8ea12fa09059", size = 48233605, upload-time = "2026-01-18T16:19:19.544Z" }, + { url = "https://files.pythonhosted.org/packages/39/07/e4e2d568cb57543d84482f61e510732820cddb0f47c4bb7df629abfed852/pyarrow-23.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:14de7d48052cf4b0ed174533eafa3cfe0711b8076ad70bede32cf59f744f0d7c", size = 50603979, upload-time = "2026-01-18T16:19:26.717Z" }, + { url = "https://files.pythonhosted.org/packages/72/9c/47693463894b610f8439b2e970b82ef81e9599c757bf2049365e40ff963c/pyarrow-23.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:427deac1f535830a744a4f04a6ac183a64fcac4341b3f618e693c41b7b98d2b0", size = 28338905, upload-time = "2026-01-18T16:19:32.93Z" }, +] + [[package]] name = "pycparser" version = "2.23" @@ -1303,6 +2011,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, ] +[[package]] +name = "pydeck" +version = "0.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/ca/40e14e196864a0f61a92abb14d09b3d3da98f94ccb03b49cf51688140dab/pydeck-0.9.1.tar.gz", hash = "sha256:f74475ae637951d63f2ee58326757f8d4f9cd9f2a457cf42950715003e2cb605", size = 3832240, upload-time = "2024-05-10T15:36:21.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/4c/b888e6cf58bd9db9c93f40d1c6be8283ff49d88919231afe93a6bcf61626/pydeck-0.9.1-py2.py3-none-any.whl", hash = "sha256:b3f75ba0d273fc917094fa61224f3f6076ca8752b93d46faf3bcfd9f9d59b038", size = 6900403, upload-time = "2024-05-10T15:36:17.36Z" }, +] + [[package]] name = "pydocket" version = "0.16.3" @@ -1405,6 +2127,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, ] +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -1432,6 +2166,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/76/03af049af4dcee5d27442f71b6924f01f3efb5d2bd34f23fcd563f2cc5f5/python_multipart-0.0.21-py3-none-any.whl", hash = "sha256:cf7a6713e01c87aa35387f4774e812c4361150938d20d232800f75ffcf266090", size = 24541, upload-time = "2025-12-17T09:24:21.153Z" }, ] +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + [[package]] name = "pywin32" version = "311" @@ -1721,8 +2464,8 @@ name = "secretstorage" version = "3.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cryptography" }, - { name = "jeepney" }, + { name = "cryptography", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, + { name = "jeepney", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1c/03/e834bcd866f2f8a49a85eaff47340affa3bfa391ee9912a952a1faa68c7b/secretstorage-3.5.0.tar.gz", hash = "sha256:f04b8e4689cbce351744d5537bf6b1329c6fc68f91fa666f60a380edddcd11be", size = 19884, upload-time = "2025-11-23T19:02:53.191Z" } wheels = [ @@ -1738,6 +2481,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + +[[package]] +name = "smmap" +version = "5.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz", hash = "sha256:26ea65a03958fa0c8a1c7e8c7a58fdc77221b8910f6be2131affade476898ad5", size = 22329, upload-time = "2025-01-02T07:14:40.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/be/d09147ad1ec7934636ad912901c5fd7667e1c858e19d355237db0d0cd5e4/smmap-5.0.2-py3-none-any.whl", hash = "sha256:b30115f0def7d7531d22a0fb6502488d879e75b260a9db4d0819cfb25403af5e", size = 24303, upload-time = "2025-01-02T07:14:38.724Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + [[package]] name = "sortedcontainers" version = "2.4.0" @@ -1773,6 +2543,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, ] +[[package]] +name = "streamlit" +version = "1.54.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "altair" }, + { name = "blinker" }, + { name = "cachetools" }, + { name = "click" }, + { name = "gitpython" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pillow" }, + { name = "protobuf" }, + { name = "pyarrow" }, + { name = "pydeck" }, + { name = "requests" }, + { name = "tenacity" }, + { name = "toml" }, + { name = "tornado" }, + { name = "typing-extensions" }, + { name = "watchdog", marker = "sys_platform != 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/be/66/d887ee80ea85f035baee607c60af024994e17ae9b921277fca9675e76ecf/streamlit-1.54.0.tar.gz", hash = "sha256:09965e6ae7eb0357091725de1ce2a3f7e4be155c2464c505c40a3da77ab69dd8", size = 8662292, upload-time = "2026-02-04T16:37:54.734Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/48/1d/40de1819374b4f0507411a60f4d2de0d620a9b10c817de5925799132b6c9/streamlit-1.54.0-py3-none-any.whl", hash = "sha256:a7b67d6293a9f5f6b4d4c7acdbc4980d7d9f049e78e404125022ecb1712f79fc", size = 9119730, upload-time = "2026-02-04T16:37:52.199Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "toml" +version = "0.10.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/be/ba/1f744cdc819428fc6b5084ec34d9b30660f6f9daaf70eead706e3203ec3c/toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f", size = 22253, upload-time = "2020-11-01T01:40:22.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/44/6f/7120676b6d73228c96e17f1f794d8ab046fc910d781c8d151120c3f1569e/toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", size = 16588, upload-time = "2020-11-01T01:40:20.672Z" }, +] + [[package]] name = "tomli" version = "2.3.0" @@ -1822,6 +2640,37 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, ] +[[package]] +name = "tornado" +version = "6.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" }, + { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" }, + { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" }, + { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" }, + { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" }, + { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + [[package]] name = "typer" version = "0.21.1" @@ -1858,6 +2707,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, ] +[[package]] +name = "tzdata" +version = "2025.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, +] + [[package]] name = "urllib3" version = "2.6.3" @@ -1881,6 +2739,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, ] +[[package]] +name = "watchdog" +version = "6.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, +] + [[package]] name = "websockets" version = "15.0.1" diff --git a/unity-mcp-skill/SKILL.md b/unity-mcp-skill/SKILL.md index b0bd08e61..5b8eb53e4 100644 --- a/unity-mcp-skill/SKILL.md +++ b/unity-mcp-skill/SKILL.md @@ -7,7 +7,7 @@ description: Orchestrate Unity Editor via MCP (Model Context Protocol) tools and This skill helps you effectively use the Unity Editor with MCP tools and resources. -## Template +## Template Notice Examples in `references/workflows.md` and `references/tools-reference.md` are reusable templates. They may be inaccurate across Unity versions, package setups (UGUI/TMP/Input System), and project-specific conventions. Please check console, compilation errors, or use screenshot after implementation. @@ -69,16 +69,40 @@ batch_execute( **Max 25 commands per batch by default (configurable in Unity MCP Tools window, max 100).** Use `fail_fast=True` for dependent operations. -### 3. Use `screenshot` in manage_scene to Verify Visual Results +### 3. Use Screenshots to Verify Visual Results ```python -# Via manage_scene -manage_scene(action="screenshot") # Returns base64 image +# Basic screenshot (saves to Assets/, returns file path only) +manage_scene(action="screenshot") -# After creating/modifying objects, verify visually: -# 1. Create objects -# 2. capture screenshot -# 3. Analyze if result matches intent +# Inline screenshot (returns base64 PNG directly to the AI) +manage_scene(action="screenshot", include_image=True) + +# Use a specific camera and cap resolution for smaller payloads +manage_scene(action="screenshot", camera="MainCamera", include_image=True, max_resolution=512) + +# Batch surround: captures front/back/left/right/top/bird_eye around the scene +manage_scene(action="screenshot", batch="surround", max_resolution=256) + +# Batch surround centered on a specific object +manage_scene(action="screenshot", batch="surround", look_at="Player", max_resolution=256) + +# Positioned screenshot: place a temp camera and capture in one call +manage_scene(action="screenshot", look_at="Player", view_position=[0, 10, -10], max_resolution=512) +``` + +**Best practices for AI scene understanding:** +- Use `include_image=True` when you need to *see* the scene, not just save a file. +- Use `batch="surround"` for a comprehensive overview (6 angles, one command). +- Use `look_at`/`view_position` to capture from a specific viewpoint without needing a scene camera. +- Keep `max_resolution` at 256–512 to balance quality vs. token cost. +- Combine with `look_at` on `manage_gameobject` to orient a game camera before capturing. + +```python +# Agentic camera loop: point, shoot, analyze +manage_gameobject(action="look_at", target="MainCamera", look_at_target="Player") +manage_scene(action="screenshot", camera="MainCamera", include_image=True, max_resolution=512) +# → Analyze image, decide next action ``` ### 4. Check Console After Major Changes @@ -148,7 +172,7 @@ uri="file:///full/path/to/file.cs" | **Editor** | `manage_editor`, `execute_menu_item`, `read_console` | Editor control | | **Testing** | `run_tests`, `get_test_job` | Unity Test Framework | | **Batch** | `batch_execute` | Parallel/bulk operations | -| **UI** | `batch_execute` with `manage_gameobject` + `manage_components` | Canvas, Panel, Button, Text, Slider, Toggle, Input Field (see [UI workflows](references/workflows.md#ui-creation-workflows)) | +| **UI** | `batch_execute` with `manage_gameobject` + `manage_components` | Canvas, Panel, Button, Text, Slider, Toggle, Input Field. **Read `mcpforunity://project/info` first** to detect uGUI/TMP/Input System availability. (see [UI workflows](references/workflows.md#ui-creation-workflows)) | ## Common Workflows diff --git a/unity-mcp-skill/references/tools-reference.md b/unity-mcp-skill/references/tools-reference.md index 0cb251155..e071dd02c 100644 --- a/unity-mcp-skill/references/tools-reference.md +++ b/unity-mcp-skill/references/tools-reference.md @@ -17,6 +17,34 @@ Complete reference for all MCP tools. Each tool includes parameters, types, and --- +## Project Info Resource + +Read `mcpforunity://project/info` to detect project capabilities before making assumptions about UI, input, or rendering setup. + +**Returned fields:** + +| Field | Type | Description | +|-------|------|-------------| +| `projectRoot` | string | Absolute path to project root | +| `projectName` | string | Project folder name | +| `unityVersion` | string | e.g. `"2022.3.20f1"` | +| `platform` | string | Active build target e.g. `"StandaloneWindows64"` | +| `assetsPath` | string | Absolute path to Assets folder | +| `renderPipeline` | string | `"BuiltIn"`, `"Universal"`, `"HighDefinition"`, or `"Custom"` | +| `activeInputHandler` | string | `"Old"`, `"New"`, or `"Both"` | +| `packages.ugui` | bool | `com.unity.ugui` installed (Canvas, Image, Button, etc.) | +| `packages.textmeshpro` | bool | `com.unity.textmeshpro` installed (TMP_Text, TMP_InputField) | +| `packages.inputsystem` | bool | `com.unity.inputsystem` installed (InputAction, PlayerInput) | + +**Key decision points:** + +- **UI system**: If `packages.ugui` is true, use Canvas + uGUI components. UI Toolkit (UIDocument/UXML) is built-in since Unity 2021+ but has no MCP tool support yet. +- **Text**: If `packages.textmeshpro` is true, use `TextMeshProUGUI` instead of legacy `Text`. +- **Input**: Use `activeInputHandler` to decide EventSystem module — `StandaloneInputModule` (Old) vs `InputSystemUIInputModule` (New). See [workflows.md — Input System](workflows.md#input-system-old-vs-new). +- **Shaders**: Use `renderPipeline` to pick correct shader names — `Standard` (BuiltIn) vs `Universal Render Pipeline/Lit` (URP) vs `HDRP/Lit` (HDRP). + +--- + ## Infrastructure Tools ### batch_execute @@ -66,7 +94,7 @@ refresh_unity( ### manage_scene -Scene CRUD operations and hierarchy queries. +Scene CRUD operations, hierarchy queries, screenshots, and scene view control. ```python # Get hierarchy (paginated) @@ -78,8 +106,47 @@ manage_scene( include_transform=False # bool - include local transforms ) -# Screenshot -manage_scene(action="screenshot") # Returns base64 PNG +# Screenshot (file only — saves to Assets/) +manage_scene(action="screenshot") + +# Screenshot with inline image (base64 PNG returned to AI) +manage_scene( + action="screenshot", + camera="MainCamera", # str, optional - camera name, path, or instance ID + include_image=True, # bool, default False - return base64 PNG inline + max_resolution=512 # int, optional - downscale cap (default 640) +) + +# Batch surround (6 angles around scene bounds, no file saved) +manage_scene( + action="screenshot", + batch="surround", # str - "surround" for 6-angle capture + max_resolution=256 # int - keep low for batch (6 images) +) +# Returns: front, back, left, right, top, bird_eye views + +# Batch surround centered on a specific target +manage_scene( + action="screenshot", + batch="surround", + look_at="Player", # str|int|list[float] - center surround on this target + max_resolution=256 +) + +# Positioned screenshot (temp camera at viewpoint, no file saved) +manage_scene( + action="screenshot", + look_at="Enemy", # str|int|list[float] - target to aim at + view_position=[0, 10, -10], # list[float], optional - camera position + view_rotation=[45, 0, 0], # list[float], optional - euler angles (overrides look_at aim) + max_resolution=512 +) + +# Frame scene view on target +manage_scene( + action="scene_view_frame", + scene_view_target="Player" # str|int - GO name, path, or instance ID to frame +) # Other actions manage_scene(action="get_active") # Current scene info @@ -166,6 +233,14 @@ manage_gameobject( distance=5.0, world_space=True ) + +# Look at target (rotates GO to face a point or another GO) +manage_gameobject( + action="look_at", + target="MainCamera", # the GO to rotate + look_at_target="Player", # str (GO name/path) or list[float] world position + look_at_up=[0, 1, 0] # optional up vector, default [0,1,0] +) ``` ### manage_components @@ -207,6 +282,24 @@ manage_components( "localScale": [2, 2, 2] } ) + +# Set object reference property (reference another GameObject by name) +manage_components( + action="set_property", + target="GameManager", + component_type="GameManagerScript", + property="targetObjects", + value=[{"name": "Flower_1"}, {"name": "Flower_2"}, {"name": "Bee_1"}] +) + +# Object reference formats supported: +# - {"name": "ObjectName"} → Find GameObject in scene by name +# - {"instanceID": 12345} → Direct instance ID reference +# - {"guid": "abc123..."} → Asset GUID reference +# - {"path": "Assets/..."} → Asset path reference +# - "Assets/Prefabs/My.prefab" → String shorthand for asset paths +# - "ObjectName" → String shorthand for scene name lookup +# - 12345 → Integer shorthand for instanceID ``` --- @@ -449,7 +542,10 @@ manage_material( action="set_renderer_color", target="MyCube", color=[1, 0, 0, 1], - mode="instance" # "shared"|"instance"|"property_block" + mode="create_unique" # Creates a unique .mat asset per object (persistent) + # Other modes: "property_block" (default, not persistent), + # "shared" (mutates shared material — avoid for primitives), + # "instance" (runtime only, not persistent) ) ``` diff --git a/unity-mcp-skill/references/workflows.md b/unity-mcp-skill/references/workflows.md index 545b5c518..5ecfb8d54 100644 --- a/unity-mcp-skill/references/workflows.md +++ b/unity-mcp-skill/references/workflows.md @@ -51,6 +51,73 @@ if editor_state["is_compiling"]: --- +## Scene Generator Build Workflow + +### Fresh Scene Before Building + +**Always start a generated scene build with `manage_scene(action="create")`** to get a clean empty scene. This avoids conflicts with existing default objects (Camera, Light) that would cause "already exists" errors when the execution plan tries to create its own. + +```python +# Step 0: Create fresh empty scene (replaces current scene entirely) +manage_scene(action="create", name="MyGeneratedScene", path="Assets/Scenes/") + +# Now proceed with the phased execution plan... +# Phase 1: Environment (camera, lights) — no conflicts +# Phase 2: Objects (GameObjects) +# Phase 3: Materials +# etc. +``` + +### Wiring Object References Between Components + +After creating scripts and attaching components, use `set_property` to wire cross-references between GameObjects. Use the `{"name": "ObjectName"}` format to reference scene objects by name: + +```python +# Wire a list of target GameObjects into a script's serialized field +manage_components( + action="set_property", + target="BeeManager", + component_type="BeeManagerScript", + property="targetObjects", + value=[{"name": "Flower_1"}, {"name": "Flower_2"}, {"name": "Flower_3"}] +) +``` + +### Physics Requirements for Trigger-Based Interactions + +When scripts use `OnTriggerEnter` / `OnTriggerStay` / `OnTriggerExit`, at least one of the two colliding objects **must** have a `Rigidbody` component. Common pattern: + +```python +# Moving objects (bees, players) need Rigidbody for triggers to fire +batch_execute(commands=[ + {"tool": "manage_components", "params": { + "action": "add", "target": "Bee_1", "component_type": "Rigidbody" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "Bee_1", + "component_type": "Rigidbody", + "properties": {"useGravity": false, "isKinematic": true} + }} +]) +``` + +### Script Overwrites with `manage_script(action="update")` + +When a generated script needs to be rewritten (e.g., to add auto-wiring logic), use `update` instead of deleting and recreating: + +```python +manage_script( + action="update", + path="Assets/Scripts/MyScript.cs", + contents="using UnityEngine;\n\npublic class MyScript : MonoBehaviour { ... }" +) +# Then refresh and check console +refresh_unity(mode="force", scope="scripts", compile="request", wait_for_ready=True) +read_console(types=["error"], count=10) +``` + +--- + ## Scene Creation Workflows ### Create Complete Scene from Scratch @@ -498,11 +565,83 @@ Unity UI (Canvas-based UGUI) requires specific component hierarchies. Use `batch > **Template warning:** This section is a skill template library, not a guaranteed source of truth. Examples may be inaccurate for your Unity version, package setup, or project conventions. > **Use safely:** -> 1. Validate component/property names against the current project. -> 2. Prefer targeting by instance ID or full path over generic names. -> 3. Assume complex controls (Slider/Toggle/TMP Input) may need extra reference wiring. +> 1. **Always read `mcpforunity://project/info` first** to detect installed packages and input system. +> 2. Validate component/property names against the current project. +> 3. Prefer targeting by instance ID or full path over generic names. > 4. Treat numeric enum values as placeholders and verify before reuse. +### Step 0: Detect Project UI Capabilities + +**Before creating any UI**, read project info to determine which packages and input system are available. + +```python +# Read mcpforunity://project/info — returns: +# { +# "renderPipeline": "BuiltIn" | "Universal" | "HighDefinition" | "Custom", +# "activeInputHandler": "Old" | "New" | "Both", +# "packages": { +# "ugui": true/false, — com.unity.ugui (Canvas, Image, Button, etc.) +# "textmeshpro": true/false, — com.unity.textmeshpro (TextMeshProUGUI) +# "inputsystem": true/false — com.unity.inputsystem (new Input System) +# } +# } +``` + +**Decision matrix:** + +| project_info field | Value | What to use | +|---|---|---| +| `packages.ugui` | `true` | Canvas-based UI (Image, Button, etc.) | +| `packages.textmeshpro` | `true` | `TextMeshProUGUI` for text | +| `packages.textmeshpro` | `false` | `UnityEngine.UI.Text` (legacy, lower quality) | +| `activeInputHandler` | `"Old"` | `StandaloneInputModule` for EventSystem | +| `activeInputHandler` | `"New"` | `InputSystemUIInputModule` for EventSystem | +| `activeInputHandler` | `"Both"` | Either works; prefer `InputSystemUIInputModule` for UI | + +### RectTransform Sizing (Critical for All UI Children) + +Every GameObject under a Canvas gets a `RectTransform` instead of `Transform`. **Without setting anchor/size, UI elements default to zero size and won't be visible.** Use `set_property` on `RectTransform`: + +```python +# Stretch to fill parent (common for panels/backgrounds) +{"tool": "manage_components", "params": { + "action": "set_property", "target": "MyPanel", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 0], # bottom-left corner + "anchorMax": [1, 1], # top-right corner + "sizeDelta": [0, 0], # no extra size beyond anchors + "anchoredPosition": [0, 0] # centered on anchors + } +}} + +# Fixed-size centered element (e.g. 300x50 button) +{"tool": "manage_components", "params": { + "action": "set_property", "target": "MyButton", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], + "anchorMax": [0.5, 0.5], + "sizeDelta": [300, 50], + "anchoredPosition": [0, 0] + } +}} + +# Top-anchored bar (e.g. health bar at top of screen) +{"tool": "manage_components", "params": { + "action": "set_property", "target": "TopBar", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 1], # left-top + "anchorMax": [1, 1], # right-top (stretch horizontally) + "sizeDelta": [0, 60], # 60px tall, full width + "anchoredPosition": [0, -30] # offset down by half height + } +}} +``` + +> **Note:** Vector2 properties accept both `[x, y]` array format and `{"x": ..., "y": ...}` object format. + ### Create Canvas (Foundation for All UI) Every UI element must be under a Canvas. A Canvas requires three components: `Canvas`, `CanvasScaler`, and `GraphicRaycaster`. @@ -541,9 +680,10 @@ batch_execute(fail_fast=True, commands=[ ### Create EventSystem (Required Once Per Scene for UI Interaction) -If no EventSystem exists in the scene, buttons and other interactive UI elements won't respond to input. Create one alongside your first Canvas. +If no EventSystem exists in the scene, buttons and other interactive UI elements won't respond to input. Create one alongside your first Canvas. **Check `project_info.activeInputHandler` to pick the correct input module.** ```python +# For activeInputHandler == "New" or "Both" (project has Input System package): batch_execute(fail_fast=True, commands=[ {"tool": "manage_gameobject", "params": { "action": "create", "name": "EventSystem" @@ -557,9 +697,22 @@ batch_execute(fail_fast=True, commands=[ "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule" }} ]) -``` -> **Note:** For projects using legacy Input Manager instead of Input System, use `"component_type": "UnityEngine.EventSystems.StandaloneInputModule"` instead. +# For activeInputHandler == "Old" (legacy Input Manager only): +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_gameobject", "params": { + "action": "create", "name": "EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.StandaloneInputModule" + }} +]) +``` ### Create Panel (Background Container) @@ -573,18 +726,26 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "MenuPanel", "component_type": "Image" }}, - # Set semi-transparent dark background {"tool": "manage_components", "params": { "action": "set_property", "target": "MenuPanel", "component_type": "Image", "property": "color", "value": [0.1, 0.1, 0.1, 0.8] + }}, + # Size the panel (stretch to 60% of canvas, centered) + {"tool": "manage_components", "params": { + "action": "set_property", "target": "MenuPanel", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.2, 0.1], "anchorMax": [0.8, 0.9], + "sizeDelta": [0, 0], "anchoredPosition": [0, 0] + } }} ]) ``` ### Create Text (TextMeshPro) -TextMeshProUGUI automatically adds a RectTransform when added to a child of a Canvas. +TextMeshProUGUI automatically adds a RectTransform when added to a child of a Canvas. If `packages.textmeshpro` is `false`, use `UnityEngine.UI.Text` instead. ```python batch_execute(fail_fast=True, commands=[ @@ -604,6 +765,14 @@ batch_execute(fail_fast=True, commands=[ "alignment": 514, "color": [1, 1, 1, 1] } + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "TitleText", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 0.8], "anchorMax": [1, 1], + "sizeDelta": [0, 0], "anchoredPosition": [0, 0] + } }} ]) ``` @@ -616,7 +785,6 @@ A Button needs an `Image` (visual) + `Button` (interaction) on the parent, and a ```python batch_execute(fail_fast=True, commands=[ - # Button container with Image + Button components {"tool": "manage_gameobject", "params": { "action": "create", "name": "StartButton", "parent": "MenuPanel" }}, @@ -631,7 +799,15 @@ batch_execute(fail_fast=True, commands=[ "component_type": "Image", "property": "color", "value": [0.2, 0.6, 1.0, 1.0] }}, - # Child text label + {"tool": "manage_components", "params": { + "action": "set_property", "target": "StartButton", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [300, 60], "anchoredPosition": [0, 0] + } + }}, + # Child text label (stretches to fill button) {"tool": "manage_gameobject", "params": { "action": "create", "name": "StartButton_Label", "parent": "StartButton" }}, @@ -643,17 +819,25 @@ batch_execute(fail_fast=True, commands=[ "action": "set_property", "target": "StartButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Start Game", "fontSize": 24, "alignment": 514} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "StartButton_Label", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0, 0], "anchorMax": [1, 1], + "sizeDelta": [0, 0], "anchoredPosition": [0, 0] + } }} ]) ``` -### Create Slider +### Create Slider (With Reference Wiring) -A Slider requires a specific hierarchy: the slider root, a background, a fill area with fill, and a handle area with handle. +A Slider requires a specific hierarchy and **must have its `fillRect` and `handleRect` references wired** to function. ```python +# Step 1: Create hierarchy batch_execute(fail_fast=True, commands=[ - # Slider root {"tool": "manage_gameobject", "params": { "action": "create", "name": "HealthSlider", "parent": "MainCanvas" }}, @@ -663,49 +847,95 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "HealthSlider", "component_type": "Image" }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "HealthSlider", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [400, 30], "anchoredPosition": [0, 0] + } + }}, # Background {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Background", "parent": "HealthSlider" + "action": "create", "name": "SliderBG", "parent": "HealthSlider" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Background", "component_type": "Image" + "action": "add", "target": "SliderBG", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Background", - "component_type": "Image", "property": "color", - "value": [0.3, 0.3, 0.3, 1.0] + "action": "set_property", "target": "SliderBG", + "component_type": "Image", "property": "color", "value": [0.3, 0.3, 0.3, 1.0] + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SliderBG", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, # Fill Area + Fill {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Fill Area", "parent": "HealthSlider" + "action": "create", "name": "FillArea", "parent": "HealthSlider" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "FillArea", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Fill", "parent": "Fill Area" + "action": "create", "name": "SliderFill", "parent": "FillArea" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Fill", "component_type": "Image" + "action": "add", "target": "SliderFill", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Fill", - "component_type": "Image", "property": "color", - "value": [0.2, 0.8, 0.2, 1.0] + "action": "set_property", "target": "SliderFill", + "component_type": "Image", "property": "color", "value": [0.2, 0.8, 0.2, 1.0] + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SliderFill", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, # Handle Area + Handle {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Handle Slide Area", "parent": "HealthSlider" + "action": "create", "name": "HandleArea", "parent": "HealthSlider" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "HandleArea", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} }}, {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Handle", "parent": "Handle Slide Area" + "action": "create", "name": "SliderHandle", "parent": "HandleArea" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "SliderHandle", "component_type": "Image" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SliderHandle", + "component_type": "RectTransform", + "properties": {"anchorMin": [0.5, 0], "anchorMax": [0.5, 1], "sizeDelta": [20, 0]} + }} +]) + +# Step 2: Wire Slider references (CRITICAL — slider won't work without this) +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_components", "params": { + "action": "set_property", "target": "HealthSlider", + "component_type": "Slider", "property": "fillRect", + "value": {"name": "SliderFill"} }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Handle", "component_type": "Image" + "action": "set_property", "target": "HealthSlider", + "component_type": "Slider", "property": "handleRect", + "value": {"name": "SliderHandle"} }} ]) ``` -### Create Input Field (TextMeshPro) +### Create Input Field (With Reference Wiring) ```python +# Step 1: Create hierarchy batch_execute(fail_fast=True, commands=[ {"tool": "manage_gameobject", "params": { "action": "create", "name": "NameInput", "parent": "MenuPanel" @@ -717,39 +947,78 @@ batch_execute(fail_fast=True, commands=[ "action": "add", "target": "NameInput", "component_type": "TMP_InputField" }}, - # Text area child + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [400, 50], "anchoredPosition": [0, 0] + } + }}, + # Text Area child (clips text to input bounds) {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Text Area", "parent": "NameInput" + "action": "create", "name": "InputTextArea", "parent": "NameInput" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "InputTextArea", "component_type": "RectMask2D" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Text Area", - "component_type": "RectMask2D" + "action": "set_property", "target": "InputTextArea", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [-16, -8]} }}, # Placeholder {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Placeholder", "parent": "Text Area" + "action": "create", "name": "InputPlaceholder", "parent": "InputTextArea" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Placeholder", - "component_type": "TextMeshProUGUI" + "action": "add", "target": "InputPlaceholder", "component_type": "TextMeshProUGUI" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Placeholder", + "action": "set_property", "target": "InputPlaceholder", "component_type": "TextMeshProUGUI", "properties": {"text": "Enter name...", "fontStyle": 2, "color": [0.5, 0.5, 0.5, 0.5]} }}, - # Actual text + {"tool": "manage_components", "params": { + "action": "set_property", "target": "InputPlaceholder", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} + }}, + # Actual text display {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Text", "parent": "Text Area" + "action": "create", "name": "InputText", "parent": "InputTextArea" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Text", - "component_type": "TextMeshProUGUI" + "action": "add", "target": "InputText", "component_type": "TextMeshProUGUI" + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "InputText", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]} + }} +]) + +# Step 2: Wire TMP_InputField references (CRITICAL) +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "TMP_InputField", "property": "textViewport", + "value": {"name": "InputTextArea"} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "TMP_InputField", "property": "textComponent", + "value": {"name": "InputText"} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "NameInput", + "component_type": "TMP_InputField", "property": "placeholder", + "value": {"name": "InputPlaceholder"} }} ]) ``` -### Create Toggle (Checkbox) +### Create Toggle (With Reference Wiring) ```python batch_execute(fail_fast=True, commands=[ @@ -759,41 +1028,72 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "SoundToggle", "component_type": "Toggle" }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SoundToggle", + "component_type": "RectTransform", + "properties": { + "anchorMin": [0.5, 0.5], "anchorMax": [0.5, 0.5], + "sizeDelta": [200, 30], "anchoredPosition": [0, 0] + } + }}, # Background box {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Background", "parent": "SoundToggle" + "action": "create", "name": "ToggleBG", "parent": "SoundToggle" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "ToggleBG", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Background", "component_type": "Image" + "action": "set_property", "target": "ToggleBG", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0.5], "anchorMax": [0, 0.5], "sizeDelta": [26, 26], "anchoredPosition": [13, 0]} }}, # Checkmark {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Checkmark", "parent": "Background" + "action": "create", "name": "ToggleCheckmark", "parent": "ToggleBG" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "ToggleCheckmark", "component_type": "Image" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Checkmark", "component_type": "Image" + "action": "set_property", "target": "ToggleCheckmark", + "component_type": "RectTransform", + "properties": {"anchorMin": [0.1, 0.1], "anchorMax": [0.9, 0.9], "sizeDelta": [0, 0]} }}, # Label {"tool": "manage_gameobject", "params": { - "action": "create", "name": "Label", "parent": "SoundToggle" + "action": "create", "name": "ToggleLabel", "parent": "SoundToggle" }}, {"tool": "manage_components", "params": { - "action": "add", "target": "Label", "component_type": "TextMeshProUGUI" + "action": "add", "target": "ToggleLabel", "component_type": "TextMeshProUGUI" }}, {"tool": "manage_components", "params": { - "action": "set_property", "target": "Label", + "action": "set_property", "target": "ToggleLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Sound Effects", "fontSize": 18, "alignment": 513} + }}, + {"tool": "manage_components", "params": { + "action": "set_property", "target": "ToggleLabel", + "component_type": "RectTransform", + "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [-35, 0], "anchoredPosition": [17.5, 0]} + }} +]) + +# Wire Toggle references (CRITICAL) +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_components", "params": { + "action": "set_property", "target": "SoundToggle", + "component_type": "Toggle", "property": "graphic", + "value": {"name": "ToggleCheckmark"} }} ]) ``` ### Add Layout Group (Vertical/Horizontal/Grid) -Layout groups auto-arrange child elements. Add to any container. +Layout groups auto-arrange child elements, so you can skip manual RectTransform positioning for children. ```python -# Vertical layout for a menu panel batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "add", "target": "MenuPanel", @@ -804,12 +1104,12 @@ batch_execute(fail_fast=True, commands=[ "component_type": "VerticalLayoutGroup", "properties": { "spacing": 10, - "childAlignment": 1, + "childAlignment": 4, "childForceExpandWidth": True, - "childForceExpandHeight": False + "childForceExpandHeight": False, + "padding": {"left": 20, "right": 20, "top": 20, "bottom": 20} } }}, - # Add ContentSizeFitter to auto-resize {"tool": "manage_components", "params": { "action": "add", "target": "MenuPanel", "component_type": "ContentSizeFitter" @@ -817,9 +1117,7 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": { "action": "set_property", "target": "MenuPanel", "component_type": "ContentSizeFitter", - "properties": { - "verticalFit": 2 - } + "properties": { "verticalFit": 2 } }} ]) ``` @@ -829,7 +1127,7 @@ batch_execute(fail_fast=True, commands=[ ### Complete Example: Main Menu Screen -Combines multiple templates into a full menu screen in two batch calls (default 25 command limit per batch, configurable in Unity MCP Tools window up to 100). +Combines multiple templates into a full menu screen in two batch calls (default 25 command limit per batch, configurable in Unity MCP Tools window up to 100). **Assumes `project_info` has been read and `activeInputHandler` is known.** ```python # Batch 1: Canvas + EventSystem + Panel + Title @@ -841,14 +1139,15 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": {"action": "add", "target": "MenuCanvas", "component_type": "GraphicRaycaster"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuCanvas", "component_type": "Canvas", "property": "renderMode", "value": 0}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuCanvas", "component_type": "CanvasScaler", "properties": {"uiScaleMode": 1, "referenceResolution": [1920, 1080]}}}, - # EventSystem + # EventSystem — use StandaloneInputModule OR InputSystemUIInputModule based on project_info {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}}, {"tool": "manage_components", "params": {"action": "add", "target": "EventSystem", "component_type": "UnityEngine.EventSystems.EventSystem"}}, {"tool": "manage_components", "params": {"action": "add", "target": "EventSystem", "component_type": "UnityEngine.EventSystems.StandaloneInputModule"}}, - # Panel + # Panel (centered, 60% width) {"tool": "manage_gameobject", "params": {"action": "create", "name": "MenuPanel", "parent": "MenuCanvas"}}, {"tool": "manage_components", "params": {"action": "add", "target": "MenuPanel", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "Image", "property": "color", "value": [0.1, 0.1, 0.15, 0.9]}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "RectTransform", "properties": {"anchorMin": [0.2, 0.15], "anchorMax": [0.8, 0.85], "sizeDelta": [0, 0]}}}, {"tool": "manage_components", "params": {"action": "add", "target": "MenuPanel", "component_type": "VerticalLayoutGroup"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "MenuPanel", "component_type": "VerticalLayoutGroup", "properties": {"spacing": 20, "childAlignment": 4, "childForceExpandWidth": True, "childForceExpandHeight": False}}}, # Title @@ -864,25 +1163,28 @@ batch_execute(fail_fast=True, commands=[ {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton", "component_type": "Button"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayButton", "component_type": "Image", "property": "color", "value": [0.2, 0.6, 1.0, 1.0]}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "PlayButton_Label", "parent": "PlayButton"}}, - {"tool": "manage_components", "params": {"action": "add", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI"}}, - {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Play", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_gameobject", "params": {"action": "create", "name": "PlayLabel", "parent": "PlayButton"}}, + {"tool": "manage_components", "params": {"action": "add", "target": "PlayLabel", "component_type": "TextMeshProUGUI"}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Play", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "PlayLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}}, # Settings Button {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsButton", "parent": "MenuPanel"}}, {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton", "component_type": "Button"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsButton", "component_type": "Image", "property": "color", "value": [0.3, 0.3, 0.35, 1.0]}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsButton_Label", "parent": "SettingsButton"}}, - {"tool": "manage_components", "params": {"action": "add", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI"}}, - {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Settings", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_gameobject", "params": {"action": "create", "name": "SettingsLabel", "parent": "SettingsButton"}}, + {"tool": "manage_components", "params": {"action": "add", "target": "SettingsLabel", "component_type": "TextMeshProUGUI"}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Settings", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "SettingsLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}}, # Quit Button {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitButton", "parent": "MenuPanel"}}, {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton", "component_type": "Image"}}, {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton", "component_type": "Button"}}, {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitButton", "component_type": "Image", "property": "color", "value": [0.8, 0.2, 0.2, 1.0]}}, - {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitButton_Label", "parent": "QuitButton"}}, - {"tool": "manage_components", "params": {"action": "add", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI"}}, - {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitButton_Label", "component_type": "TextMeshProUGUI", "properties": {"text": "Quit", "fontSize": 32, "alignment": 514}}} + {"tool": "manage_gameobject", "params": {"action": "create", "name": "QuitLabel", "parent": "QuitButton"}}, + {"tool": "manage_components", "params": {"action": "add", "target": "QuitLabel", "component_type": "TextMeshProUGUI"}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitLabel", "component_type": "TextMeshProUGUI", "properties": {"text": "Quit", "fontSize": 32, "alignment": 514}}}, + {"tool": "manage_components", "params": {"action": "set_property", "target": "QuitLabel", "component_type": "RectTransform", "properties": {"anchorMin": [0, 0], "anchorMax": [1, 1], "sizeDelta": [0, 0]}}} ]) ``` @@ -891,17 +1193,130 @@ batch_execute(fail_fast=True, commands=[ | UI Element | Required Components | Notes | | ---------- | ------------------- | ----- | | **Canvas** | Canvas + CanvasScaler + GraphicRaycaster | Root for all UI. One per screen. | -| **EventSystem** | EventSystem + StandaloneInputModule (or InputSystemUIInputModule) | One per scene. Required for interaction. | -| **Panel** | Image | Container. Set color for background. | -| **Text** | TextMeshProUGUI | Auto-adds RectTransform under Canvas. | -| **Button** | Image + Button + child(TextMeshProUGUI) | Image = visual, Button = click handler. | -| **Image** | Image | Set sprite property for custom graphics. | -| **Slider** | Slider + Image + children(Background, Fill Area/Fill, Handle Slide Area/Handle) | Complex hierarchy. | -| **Toggle** | Toggle + children(Background/Checkmark, Label) | Checkbox/radio button. | -| **Input Field** | Image + TMP_InputField + children(Text Area/Placeholder/Text) | Text input. | -| **Scroll View** | ScrollRect + Image + children(Viewport/Content, Scrollbar) | Scrollable container. | -| **Dropdown** | Image + TMP_Dropdown + children(Label, Arrow, Template) | Selection menu. | -| **Layout Group** | VerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroup | Add to any container to auto-arrange children. | +| **EventSystem** | EventSystem + input module (see below) | One per scene. Required for interaction. | +| **Panel** | Image + RectTransform sizing | Container. Set color for background. | +| **Text** | TextMeshProUGUI (or Text if no TMP) + RectTransform | Check `packages.textmeshpro`. | +| **Button** | Image + Button + child(TextMeshProUGUI) + RectTransform | Image = visual, Button = click handler. | +| **Slider** | Slider + Image + children + **wire fillRect/handleRect** | Won't function without wiring. | +| **Toggle** | Toggle + children + **wire graphic** | Wire checkmark Image to `graphic`. | +| **Input Field** | Image + TMP_InputField + children + **wire textViewport/textComponent/placeholder** | Won't function without wiring. | +| **Layout Group** | VerticalLayoutGroup / HorizontalLayoutGroup / GridLayoutGroup | Auto-arranges children; skip manual RectTransform on children. | + +--- + +## Input System: Old vs New + +Unity has two input systems that affect UI interaction, script input handling, and EventSystem configuration. **Always check `project_info.activeInputHandler` before creating EventSystems or writing input code.** + +### Detection + +```python +# Read mcpforunity://project/info +# activeInputHandler: "Old" | "New" | "Both" +# packages.inputsystem: true/false (whether com.unity.inputsystem is installed) +``` + +### EventSystem — Old Input Manager + +Used when `activeInputHandler` is `"Old"`. Uses `StandaloneInputModule` which reads from `Input.GetAxis()` / `Input.GetButton()`. + +```python +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.StandaloneInputModule" + }} +]) +``` + +Script pattern (old Input Manager): + +```csharp +// Input.GetAxis / Input.GetKey — works with old Input Manager +void Update() +{ + float h = Input.GetAxis("Horizontal"); + float v = Input.GetAxis("Vertical"); + transform.Translate(new Vector3(h, 0, v) * speed * Time.deltaTime); + + if (Input.GetKeyDown(KeyCode.Space)) + Jump(); + + if (Input.GetMouseButtonDown(0)) + Fire(); +} +``` + +### EventSystem — New Input System + +Used when `activeInputHandler` is `"New"` or `"Both"`. Uses `InputSystemUIInputModule` from the `com.unity.inputsystem` package. + +```python +batch_execute(fail_fast=True, commands=[ + {"tool": "manage_gameobject", "params": {"action": "create", "name": "EventSystem"}}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.EventSystems.EventSystem" + }}, + {"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule" + }} +]) +``` + +Script pattern (new Input System with `PlayerInput` component): + +```csharp +using UnityEngine; +using UnityEngine.InputSystem; + +public class PlayerController : MonoBehaviour +{ + public float speed = 5f; + private Vector2 moveInput; + + // Called by PlayerInput component via SendMessages or UnityEvents + public void OnMove(InputValue value) + { + moveInput = value.Get(); + } + + public void OnJump(InputValue value) + { + if (value.isPressed) + Jump(); + } + + void Update() + { + Vector3 move = new Vector3(moveInput.x, 0, moveInput.y); + transform.Translate(move * speed * Time.deltaTime); + } +} +``` + +### When `activeInputHandler` is `"Both"` + +Both systems are active simultaneously. For UI, prefer `InputSystemUIInputModule`. For gameplay scripts, either approach works — `Input.GetAxis()` still functions alongside the new Input System. + +```python +# UI: use new Input System module +{"tool": "manage_components", "params": { + "action": "add", "target": "EventSystem", + "component_type": "UnityEngine.InputSystem.UI.InputSystemUIInputModule" +}} + +# Gameplay scripts: Input.GetAxis() still works in "Both" mode +# But prefer the new Input System for consistency +``` + +> **Gotcha:** Adding `StandaloneInputModule` when `activeInputHandler` is `"New"` will cause a runtime error. Always check first. ---