Spaces:
Sleeping
Sleeping
Sandra Sanchez commited on
Commit ·
c7d933a
1
Parent(s): af826f9
Integrate mcp server into Claude desktop SDK by editing config and using easy formula with fastmcp
Browse files- app.py +20 -72
- claude_mcp_tools.json +0 -53
- launcher.py +0 -33
- mcp_api.py +0 -61
- mcp_server/__init__.py +0 -0
- mcp_server/logic.py +0 -108
- pyproject.toml +1 -0
- server.py +284 -0
- uv.lock +352 -4
app.py
CHANGED
|
@@ -7,9 +7,8 @@ from dotenv import load_dotenv
|
|
| 7 |
import base64
|
| 8 |
import io
|
| 9 |
from PIL import Image
|
| 10 |
-
import asyncio
|
| 11 |
-
from mcp_server.logic import create_mcp_server
|
| 12 |
import tempfile
|
|
|
|
| 13 |
|
| 14 |
# Load environment variables from .env file
|
| 15 |
load_dotenv()
|
|
@@ -20,15 +19,9 @@ client = OpenAI(api_key=OPENAI_API_KEY)
|
|
| 20 |
TEMPLATES_DIR = Path(__file__).resolve().parent / "mcp_server" / "templates"
|
| 21 |
TEMP_DIR = Path(__file__).resolve().parent / "temp"
|
| 22 |
TEMP_DIR.mkdir(exist_ok=True)
|
|
|
|
| 23 |
|
| 24 |
-
VIBE_DESCRIPTIONS
|
| 25 |
-
"Comic": "comic-style illustration, comic strip format with exactly 4 panels, bold outlines, dynamic poses, speech bubbles, vibrant colors. If there is text, translate it to the selected language with perfect ortography",
|
| 26 |
-
"Kawaii": "kawaii-style illustration, pastel colors, cute rounded characters, big eyes.",
|
| 27 |
-
"Pictorial": "pictorial illustration, painted with a brush, gentle and calm atmosphere",
|
| 28 |
-
"Basic B&W": "black and white illustration, simple lines, no other colors allowed, like a coloring book.Absolutely only use black and white.",
|
| 29 |
-
"Cartoon": "cartoon-style illustration, exaggerated features, bright colors, playful mood",
|
| 30 |
-
"Soft Pastel": "Only use soft pastel colours, smooth textures, calming feeling"
|
| 31 |
-
}
|
| 32 |
|
| 33 |
def load_scenarios():
|
| 34 |
# Returns the list of available scenarios
|
|
@@ -58,75 +51,28 @@ def generate_story_base(scenario_name: str, language="en", culture="default", ag
|
|
| 58 |
model="gpt-4o-mini",
|
| 59 |
messages=[{"role": "user", "content": story_prompt}]
|
| 60 |
)
|
| 61 |
-
|
| 62 |
-
|
|
|
|
| 63 |
|
| 64 |
-
|
| 65 |
-
# Generates the illustration based on the story
|
| 66 |
-
scenario_file = scenario_name.replace(" ", "_")
|
| 67 |
-
filepath = TEMPLATES_DIR / f"{scenario_file}.json"
|
| 68 |
-
if not filepath.exists():
|
| 69 |
-
return None
|
| 70 |
-
template = json.loads(filepath.read_text())
|
| 71 |
-
print(f"[DEBUG] Template loaded for image: {template}")
|
| 72 |
-
vibe_desc = VIBE_DESCRIPTIONS.get(vibe, vibe)
|
| 73 |
-
# Traducir solo la historia si es necesario
|
| 74 |
-
story_for_image = story_text
|
| 75 |
-
lang = language if language is not None else "en"
|
| 76 |
-
if lang.lower() != "en":
|
| 77 |
-
try:
|
| 78 |
-
loop = asyncio.get_event_loop()
|
| 79 |
-
except RuntimeError:
|
| 80 |
-
loop = asyncio.new_event_loop()
|
| 81 |
-
asyncio.set_event_loop(loop)
|
| 82 |
-
story_for_image = loop.run_until_complete(call_translate_tool(story_text, lang))
|
| 83 |
-
print(f"[DEBUG] Translated story for image:\n{story_for_image}")
|
| 84 |
-
if vibe == "Comic":
|
| 85 |
-
image_prompt = (
|
| 86 |
-
f"STORY (in {lang}): {story_for_image}\n"
|
| 87 |
-
f"INSTRUCTIONS (in English): FORMAT: Create a comic strip with exactly 4 panels. Each panel should have at most one speech bubble, and no more than 3 speech bubbles in total. Each speech bubble should contain only 1 to 3 words, in perfect {lang} orthography. The comic must visually and textually reflect the story above. Show a child of age {age}, gender {gender}, and the comfort character {comfort_character} together in a safe, public, and friendly environment. Do not include any other animals. Use the story as inspiration for the actions and words in the comic. Context: {template['title']}. Culture: {culture}. Illustration style: {vibe_desc}."
|
| 88 |
-
)
|
| 89 |
-
else:
|
| 90 |
-
image_prompt = (
|
| 91 |
-
f"STORY (in {lang}): {story_for_image}\n"
|
| 92 |
-
f"INSTRUCTIONS (in English): FORMAT: This is NOT a comic. Do NOT use panels, speech bubbles, or any text. Only draw a single illustration. Create an illustration in the style '{vibe}': {vibe_desc}. Show a child of age {age}, gender {gender}, and the comfort character {comfort_character} together in a safe, public and friendly environment. Do not include any other animals. Context: {template['title']}. Culture: {culture}. Only visual elements, no text. Key features: 5 visual features of the style. TONE AND MOOD: Gentle, supportive, calming. Friendly characters."
|
| 93 |
-
)
|
| 94 |
-
print(f"[DEBUG] Image prompt:\n{image_prompt}\nLanguage: {lang}")
|
| 95 |
-
img = client.images.generate(
|
| 96 |
-
model="gpt-image-1",
|
| 97 |
-
prompt=image_prompt,
|
| 98 |
-
output_format="png",
|
| 99 |
-
size="1024x1024"
|
| 100 |
-
)
|
| 101 |
-
# Decode image from base64
|
| 102 |
-
image_bytes = base64.b64decode(img.data[0].b64_json)
|
| 103 |
-
image = Image.open(io.BytesIO(image_bytes))
|
| 104 |
-
return image
|
| 105 |
|
| 106 |
def show_selected(scenario_name):
|
| 107 |
# Shows the selected scenario
|
| 108 |
return f"You selected: {scenario_name}"
|
| 109 |
|
| 110 |
-
|
|
|
|
| 111 |
print(f"\n[TOOL] Adapt story called with:\nCulture: {culture}, Age: {age}, Gender: {gender}, Vibe: {vibe}, Comfort Character: {comfort_character}\nStory:\n{story}\n")
|
| 112 |
-
|
| 113 |
-
context = {}
|
| 114 |
-
result = await server.request_handlers["adapt_story"](context, story, culture, age, gender, vibe, comfort_character)
|
| 115 |
-
return result["adapted_story"]
|
| 116 |
|
| 117 |
-
|
| 118 |
print(f"\n[TOOL] Translate story called with:\nLanguage: {language}\nGender: {gender}\nStory:\n{story}\n")
|
| 119 |
-
|
| 120 |
-
context = {}
|
| 121 |
-
result = await server.request_handlers["translate_story"](context, story, language, gender)
|
| 122 |
-
return result["translated_story"]
|
| 123 |
|
| 124 |
-
|
| 125 |
print(f"\n[TOOL] Generate voice called with:\nLanguage: {language}\nStory:\n{story}\n")
|
| 126 |
-
|
| 127 |
-
context = {}
|
| 128 |
-
result = await server.request_handlers["generate_voice"](context, story, language)
|
| 129 |
-
return result.get("audio", None)
|
| 130 |
|
| 131 |
def format_story(story):
|
| 132 |
# Formats the text for large, clear display, without asterisks
|
|
@@ -173,9 +119,9 @@ def main():
|
|
| 173 |
story_base = generate_story_base(
|
| 174 |
scenario_name, language, culture, age, gender, vibe, comfort_character
|
| 175 |
)
|
| 176 |
-
adapted_story =
|
| 177 |
if language != "en":
|
| 178 |
-
final_story =
|
| 179 |
else:
|
| 180 |
final_story = adapted_story
|
| 181 |
return format_story(final_story), final_story
|
|
@@ -191,7 +137,9 @@ def main():
|
|
| 191 |
def on_generate_image(scenario_name, culture, age, gender, vibe, comfort_character, story_text, language):
|
| 192 |
print(f"[DEBUG] Received args in on_generate_image: scenario_name={scenario_name}, culture={culture}, age={age}, gender={gender}, vibe={vibe}, comfort_character={comfort_character}, language={language}")
|
| 193 |
print(f"[DEBUG] Story text for image:\n{story_text}")
|
| 194 |
-
|
|
|
|
|
|
|
| 195 |
|
| 196 |
image_btn.click(
|
| 197 |
fn=on_generate_image,
|
|
@@ -207,7 +155,7 @@ def main():
|
|
| 207 |
def on_voice(story, language):
|
| 208 |
if not story or story.strip() == "":
|
| 209 |
return None
|
| 210 |
-
audio_bytes =
|
| 211 |
if audio_bytes:
|
| 212 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3", dir=str(TEMP_DIR)) as tmp_file:
|
| 213 |
tmp_file.write(audio_bytes)
|
|
|
|
| 7 |
import base64
|
| 8 |
import io
|
| 9 |
from PIL import Image
|
|
|
|
|
|
|
| 10 |
import tempfile
|
| 11 |
+
from server import adapt_story, translate_story, generate_voice, generate_image as generate_image_mcp
|
| 12 |
|
| 13 |
# Load environment variables from .env file
|
| 14 |
load_dotenv()
|
|
|
|
| 19 |
TEMPLATES_DIR = Path(__file__).resolve().parent / "mcp_server" / "templates"
|
| 20 |
TEMP_DIR = Path(__file__).resolve().parent / "temp"
|
| 21 |
TEMP_DIR.mkdir(exist_ok=True)
|
| 22 |
+
GENERATED_IMAGES_DIR = Path(__file__).resolve().parent / "generated_images"
|
| 23 |
|
| 24 |
+
# VIBE_DESCRIPTIONS moved to server.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
def load_scenarios():
|
| 27 |
# Returns the list of available scenarios
|
|
|
|
| 51 |
model="gpt-4o-mini",
|
| 52 |
messages=[{"role": "user", "content": story_prompt}]
|
| 53 |
)
|
| 54 |
+
story_base = story_resp.choices[0].message.content.strip()
|
| 55 |
+
print(f"[DEBUG] Story response:\n{story_base}")
|
| 56 |
+
return story_base
|
| 57 |
|
| 58 |
+
# generate_image now imported from server.py as generate_image_mcp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
| 60 |
def show_selected(scenario_name):
|
| 61 |
# Shows the selected scenario
|
| 62 |
return f"You selected: {scenario_name}"
|
| 63 |
|
| 64 |
+
# Direct tool calls (no async needed)
|
| 65 |
+
def call_adapt_tool(story, culture, age, gender, vibe, comfort_character):
|
| 66 |
print(f"\n[TOOL] Adapt story called with:\nCulture: {culture}, Age: {age}, Gender: {gender}, Vibe: {vibe}, Comfort Character: {comfort_character}\nStory:\n{story}\n")
|
| 67 |
+
return adapt_story(story, culture, age, gender, vibe, comfort_character)
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
def call_translate_tool(story, language, gender="female"):
|
| 70 |
print(f"\n[TOOL] Translate story called with:\nLanguage: {language}\nGender: {gender}\nStory:\n{story}\n")
|
| 71 |
+
return translate_story(story, language, gender)
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
+
def call_voice_tool(story, language):
|
| 74 |
print(f"\n[TOOL] Generate voice called with:\nLanguage: {language}\nStory:\n{story}\n")
|
| 75 |
+
return generate_voice(story, language)
|
|
|
|
|
|
|
|
|
|
| 76 |
|
| 77 |
def format_story(story):
|
| 78 |
# Formats the text for large, clear display, without asterisks
|
|
|
|
| 119 |
story_base = generate_story_base(
|
| 120 |
scenario_name, language, culture, age, gender, vibe, comfort_character
|
| 121 |
)
|
| 122 |
+
adapted_story = call_adapt_tool(story_base, culture, age, gender, vibe, comfort_character)
|
| 123 |
if language != "en":
|
| 124 |
+
final_story = call_translate_tool(adapted_story, language, gender)
|
| 125 |
else:
|
| 126 |
final_story = adapted_story
|
| 127 |
return format_story(final_story), final_story
|
|
|
|
| 137 |
def on_generate_image(scenario_name, culture, age, gender, vibe, comfort_character, story_text, language):
|
| 138 |
print(f"[DEBUG] Received args in on_generate_image: scenario_name={scenario_name}, culture={culture}, age={age}, gender={gender}, vibe={vibe}, comfort_character={comfort_character}, language={language}")
|
| 139 |
print(f"[DEBUG] Story text for image:\n{story_text}")
|
| 140 |
+
image_path = generate_image_mcp(scenario_name, culture, age, gender, vibe, comfort_character, story_text, language)
|
| 141 |
+
# Load and return the image
|
| 142 |
+
return Image.open(image_path)
|
| 143 |
|
| 144 |
image_btn.click(
|
| 145 |
fn=on_generate_image,
|
|
|
|
| 155 |
def on_voice(story, language):
|
| 156 |
if not story or story.strip() == "":
|
| 157 |
return None
|
| 158 |
+
audio_bytes = call_voice_tool(story, language)
|
| 159 |
if audio_bytes:
|
| 160 |
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3", dir=str(TEMP_DIR)) as tmp_file:
|
| 161 |
tmp_file.write(audio_bytes)
|
claude_mcp_tools.json
DELETED
|
@@ -1,53 +0,0 @@
|
|
| 1 |
-
[
|
| 2 |
-
{
|
| 3 |
-
"name": "adapt_story",
|
| 4 |
-
"description": "Adapts a story for an autistic child based on culture, age, gender, and comfort character.",
|
| 5 |
-
"endpoint": "http://127.0.0.1:8000/adapt_story",
|
| 6 |
-
"method": "POST",
|
| 7 |
-
"args": {
|
| 8 |
-
"story": "string",
|
| 9 |
-
"culture": "string",
|
| 10 |
-
"age": "string",
|
| 11 |
-
"gender": "string",
|
| 12 |
-
"vibe": "string",
|
| 13 |
-
"comfort_character": "string"
|
| 14 |
-
}
|
| 15 |
-
},
|
| 16 |
-
{
|
| 17 |
-
"name": "translate_story",
|
| 18 |
-
"description": "Translates a story to the selected language and gender.",
|
| 19 |
-
"endpoint": "http://127.0.0.1:8000/translate_story",
|
| 20 |
-
"method": "POST",
|
| 21 |
-
"args": {
|
| 22 |
-
"story": "string",
|
| 23 |
-
"language": "string",
|
| 24 |
-
"gender": "string"
|
| 25 |
-
}
|
| 26 |
-
},
|
| 27 |
-
{
|
| 28 |
-
"name": "generate_voice",
|
| 29 |
-
"description": "Generates audio for the story in the selected language.",
|
| 30 |
-
"endpoint": "http://127.0.0.1:8000/generate_voice",
|
| 31 |
-
"method": "POST",
|
| 32 |
-
"args": {
|
| 33 |
-
"story": "string",
|
| 34 |
-
"language": "string"
|
| 35 |
-
}
|
| 36 |
-
},
|
| 37 |
-
{
|
| 38 |
-
"name": "generate_image",
|
| 39 |
-
"description": "Generates an illustration based on the story and parameters.",
|
| 40 |
-
"endpoint": "http://127.0.0.1:8000/generate_image",
|
| 41 |
-
"method": "POST",
|
| 42 |
-
"args": {
|
| 43 |
-
"scenario_name": "string",
|
| 44 |
-
"culture": "string",
|
| 45 |
-
"age": "string",
|
| 46 |
-
"gender": "string",
|
| 47 |
-
"vibe": "string",
|
| 48 |
-
"comfort_character": "string",
|
| 49 |
-
"story_text": "string",
|
| 50 |
-
"language": "string"
|
| 51 |
-
}
|
| 52 |
-
}
|
| 53 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
launcher.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
# launcher.py
|
| 2 |
-
import asyncio
|
| 3 |
-
from mcp.server.stdio import stdio_server
|
| 4 |
-
from mcp_server.logic import create_mcp_server
|
| 5 |
-
import logging
|
| 6 |
-
import sys
|
| 7 |
-
import os
|
| 8 |
-
|
| 9 |
-
logging.basicConfig(level=logging.INFO)
|
| 10 |
-
logger = logging.getLogger(__name__)
|
| 11 |
-
|
| 12 |
-
# Add project root to path
|
| 13 |
-
ROOT = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
-
sys.path.append(ROOT)
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
async def main():
|
| 18 |
-
logger.info("Starting MCP server...")
|
| 19 |
-
server = await create_mcp_server()
|
| 20 |
-
async with stdio_server() as (read_stream, write_stream):
|
| 21 |
-
logger.info("Server connected, running...")
|
| 22 |
-
await server.run(
|
| 23 |
-
read_stream,
|
| 24 |
-
write_stream,
|
| 25 |
-
server.create_initialization_options()
|
| 26 |
-
)
|
| 27 |
-
|
| 28 |
-
print("[MCP] MCP server started.")
|
| 29 |
-
|
| 30 |
-
if __name__ == "__main__":
|
| 31 |
-
asyncio.run(main())
|
| 32 |
-
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mcp_api.py
DELETED
|
@@ -1,61 +0,0 @@
|
|
| 1 |
-
from fastapi import FastAPI, Request
|
| 2 |
-
from mcp_server.logic import create_mcp_server
|
| 3 |
-
import asyncio
|
| 4 |
-
import sys
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
print("MCP server starting...", file=sys.stderr)
|
| 8 |
-
|
| 9 |
-
app = FastAPI()
|
| 10 |
-
|
| 11 |
-
@app.post("/adapt_story")
|
| 12 |
-
async def adapt_story_endpoint(request: Request):
|
| 13 |
-
data = await request.json()
|
| 14 |
-
story = data.get("story")
|
| 15 |
-
culture = data.get("culture", "default")
|
| 16 |
-
age = data.get("age", "7")
|
| 17 |
-
gender = data.get("gender", "female")
|
| 18 |
-
vibe = data.get("vibe", "Cartoon")
|
| 19 |
-
comfort_character = data.get("comfort_character", "Koala")
|
| 20 |
-
server = await create_mcp_server()
|
| 21 |
-
context = {}
|
| 22 |
-
result = await server.request_handlers["adapt_story"](context, story, culture, age, gender, vibe, comfort_character)
|
| 23 |
-
return result
|
| 24 |
-
|
| 25 |
-
@app.post("/translate_story")
|
| 26 |
-
async def translate_story_endpoint(request: Request):
|
| 27 |
-
data = await request.json()
|
| 28 |
-
story = data.get("story")
|
| 29 |
-
language = data.get("language", "en")
|
| 30 |
-
gender = data.get("gender", "female")
|
| 31 |
-
server = await create_mcp_server()
|
| 32 |
-
context = {}
|
| 33 |
-
result = await server.request_handlers["translate_story"](context, story, language, gender)
|
| 34 |
-
return result
|
| 35 |
-
|
| 36 |
-
@app.post("/generate_voice")
|
| 37 |
-
async def generate_voice_endpoint(request: Request):
|
| 38 |
-
data = await request.json()
|
| 39 |
-
story = data.get("story")
|
| 40 |
-
language = data.get("language", "en")
|
| 41 |
-
server = await create_mcp_server()
|
| 42 |
-
context = {}
|
| 43 |
-
result = await server.request_handlers["generate_voice"](context, story, language)
|
| 44 |
-
return result
|
| 45 |
-
|
| 46 |
-
@app.post("/generate_image")
|
| 47 |
-
async def generate_image_endpoint(request: Request):
|
| 48 |
-
data = await request.json()
|
| 49 |
-
scenario_name = data.get("scenario_name")
|
| 50 |
-
culture = data.get("culture", "default")
|
| 51 |
-
age = data.get("age", "7")
|
| 52 |
-
gender = data.get("gender", "female")
|
| 53 |
-
vibe = data.get("vibe", "Cartoon")
|
| 54 |
-
comfort_character = data.get("comfort_character", "Koala")
|
| 55 |
-
story_text = data.get("story_text")
|
| 56 |
-
language = data.get("language", "en")
|
| 57 |
-
from app import generate_image
|
| 58 |
-
image = generate_image(scenario_name, culture, age, gender, vibe, comfort_character, story_text, language)
|
| 59 |
-
return {"image": "[image generated]"}
|
| 60 |
-
|
| 61 |
-
# Para ejecutar: uvicorn mcp_api:app --reload
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
mcp_server/__init__.py
DELETED
|
File without changes
|
mcp_server/logic.py
DELETED
|
@@ -1,108 +0,0 @@
|
|
| 1 |
-
# mcp_server/server.py
|
| 2 |
-
from mcp.server import Server
|
| 3 |
-
from mcp.server.stdio import stdio_server
|
| 4 |
-
import os
|
| 5 |
-
from openai import OpenAI
|
| 6 |
-
|
| 7 |
-
async def create_mcp_server():
|
| 8 |
-
server = Server(
|
| 9 |
-
name="comfortool-mcp"
|
| 10 |
-
)
|
| 11 |
-
|
| 12 |
-
# If your SDK has setters, use them:
|
| 13 |
-
if hasattr(server, "set_description"):
|
| 14 |
-
server.set_description("MCP server for comfortool")
|
| 15 |
-
|
| 16 |
-
if hasattr(server, "set_version"):
|
| 17 |
-
server.set_version("0.1.0")
|
| 18 |
-
|
| 19 |
-
# Tool: Adapt a story for autism and culture
|
| 20 |
-
async def adapt_story(context, story, culture="default", age="7", gender="female", vibe="Cartoon", comfort_character="Koala"):
|
| 21 |
-
prompt = (
|
| 22 |
-
f"Adapt the following story for an autistic child of age {age}, gender {gender}, from {culture} culture."
|
| 23 |
-
f"Include '{comfort_character}' as a supportive friend, use the style '{vibe}', and make the story concrete, supportive, and easy to understand."
|
| 24 |
-
"Avoid excessive emotion and exclamation marks."
|
| 25 |
-
"Adapt language according to age, simpler sentences for smaller (ages 2 to 4)kids"
|
| 26 |
-
"The absolute max length is 640 characters"
|
| 27 |
-
"Do not mention any info related to format, like pages, panels or scenes. It is just one story"
|
| 28 |
-
"Do not translate. Return only the adapted story in English."
|
| 29 |
-
f"\n\nStory:\n{story}"
|
| 30 |
-
)
|
| 31 |
-
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
| 32 |
-
client = OpenAI(api_key=openai_api_key)
|
| 33 |
-
response = client.chat.completions.create(
|
| 34 |
-
model="gpt-4o-mini",
|
| 35 |
-
messages=[{"role": "user", "content": prompt}]
|
| 36 |
-
)
|
| 37 |
-
adapted_story = response.choices[0].message.content.strip()
|
| 38 |
-
return {"adapted_story": adapted_story}
|
| 39 |
-
|
| 40 |
-
# Tool: Translate a story
|
| 41 |
-
async def translate_story(context, story, language="es-ES", gender="female"):
|
| 42 |
-
prompt = (
|
| 43 |
-
f"Translate the following story to {language} and the corresponding regional or cultural variant if mentioned. Use correct grammatical gender and pronouns, adapted to the selected gender of the kid ('{gender}'). "
|
| 44 |
-
"If the story mentions an animal or character, translate its name to the most culturally and linguistically appropriate version for the target language and region. "
|
| 45 |
-
"For example, if the story mentions 'fox', translate it as 'zorro' for Spanish from Spain. "
|
| 46 |
-
"Return only the translated story, using the regional variety requested. "
|
| 47 |
-
f"\n\nStory:\n{story}"
|
| 48 |
-
)
|
| 49 |
-
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
| 50 |
-
client = OpenAI(api_key=openai_api_key)
|
| 51 |
-
response = client.chat.completions.create(
|
| 52 |
-
model="gpt-4o-mini",
|
| 53 |
-
messages=[{"role": "user", "content": prompt}]
|
| 54 |
-
)
|
| 55 |
-
translated_story = response.choices[0].message.content.strip()
|
| 56 |
-
return {"translated_story": translated_story}
|
| 57 |
-
|
| 58 |
-
# Tool: Adaptive TTS with LLM-based smart selection
|
| 59 |
-
VALID_VOICES = {"nova", "shimmer", "echo", "onyx", "fable", "alloy", "ash", "sage", "coral"}
|
| 60 |
-
|
| 61 |
-
async def generate_voice(context, story, language="en"):
|
| 62 |
-
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
| 63 |
-
client = OpenAI(api_key=openai_api_key)
|
| 64 |
-
prompt = (
|
| 65 |
-
f"Read the following story aloud for a child in language '{language}'. "
|
| 66 |
-
"Select ONLY one of these OpenAI voices: nova, shimmer, echo, onyx, fable, alloy, ash, sage, coral. "
|
| 67 |
-
"Adapt pronunciation and prosody to match the regional variety (for example, Spanish from Spain vs. Mexico, English US vs. UK) with its specific phonemes, lie Spanish Z. "
|
| 68 |
-
"Decide what is the right pronunciation choice in case there are foreign words, like talking about a corgi dog in Spanish"
|
| 69 |
-
"If no specific voice is available, use the default for the language. "
|
| 70 |
-
"Make the speech clear, friendly, and easy to understand for autistic children. "
|
| 71 |
-
"Return only the name of the selected voice in the format: Voice: <voice_name>."
|
| 72 |
-
f"\n\nStory:\n{story}"
|
| 73 |
-
)
|
| 74 |
-
response = client.chat.completions.create(
|
| 75 |
-
model="gpt-4o-mini",
|
| 76 |
-
messages=[{"role": "user", "content": prompt}]
|
| 77 |
-
)
|
| 78 |
-
voice_decision = response.choices[0].message.content.strip()
|
| 79 |
-
print(f"[VOICE LLM DECISION]: {voice_decision}")
|
| 80 |
-
|
| 81 |
-
voice = "nova" # Default value
|
| 82 |
-
for line in voice_decision.splitlines():
|
| 83 |
-
if line.lower().startswith("voice:"):
|
| 84 |
-
candidate = line.split(":", 1)[1].strip().strip('"').strip("'")
|
| 85 |
-
if candidate in VALID_VOICES:
|
| 86 |
-
voice = candidate
|
| 87 |
-
else:
|
| 88 |
-
print(f"[VOICE WARNING] '{candidate}' is not a valid OpenAI voice. Using default: 'nova'.")
|
| 89 |
-
break
|
| 90 |
-
|
| 91 |
-
tts_response = client.audio.speech.create(
|
| 92 |
-
model="tts-1",
|
| 93 |
-
voice=voice,
|
| 94 |
-
input=story,
|
| 95 |
-
response_format="mp3"
|
| 96 |
-
)
|
| 97 |
-
audio_bytes = tts_response.content
|
| 98 |
-
return {"audio": audio_bytes}
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
server.request_handlers["adapt_story"] = adapt_story
|
| 102 |
-
server.request_handlers["translate_story"] = translate_story
|
| 103 |
-
server.request_handlers["generate_voice"] = generate_voice
|
| 104 |
-
|
| 105 |
-
return server
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pyproject.toml
CHANGED
|
@@ -14,6 +14,7 @@ dependencies = [
|
|
| 14 |
"uvicorn>=0.38.0",
|
| 15 |
"mcp>=1.22.0",
|
| 16 |
"openai>=2.8.1",
|
|
|
|
| 17 |
]
|
| 18 |
|
| 19 |
[tool.uv]
|
|
|
|
| 14 |
"uvicorn>=0.38.0",
|
| 15 |
"mcp>=1.22.0",
|
| 16 |
"openai>=2.8.1",
|
| 17 |
+
"fastmcp>=2.13.2",
|
| 18 |
]
|
| 19 |
|
| 20 |
[tool.uv]
|
server.py
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Comfortool MCP Server
|
| 3 |
+
Provides tools for creating autism-friendly social stories with cultural adaptation
|
| 4 |
+
"""
|
| 5 |
+
from fastmcp import FastMCP
|
| 6 |
+
from openai import OpenAI
|
| 7 |
+
import os
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
import base64
|
| 10 |
+
import io
|
| 11 |
+
from PIL import Image
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
import json
|
| 14 |
+
|
| 15 |
+
# Load environment variables
|
| 16 |
+
load_dotenv()
|
| 17 |
+
|
| 18 |
+
# Initialize FastMCP server
|
| 19 |
+
mcp = FastMCP("comfortool")
|
| 20 |
+
|
| 21 |
+
# Constants
|
| 22 |
+
TEMPLATES_DIR = Path(__file__).resolve().parent / "mcp_server" / "templates"
|
| 23 |
+
GENERATED_IMAGES_DIR = Path(__file__).resolve().parent / "generated_images"
|
| 24 |
+
GENERATED_IMAGES_DIR.mkdir(exist_ok=True)
|
| 25 |
+
|
| 26 |
+
VIBE_DESCRIPTIONS = {
|
| 27 |
+
"Comic": "comic-style illustration, comic strip format with exactly 4 panels, bold outlines, dynamic poses, speech bubbles, vibrant colors. If there is text, translate it to the selected language with perfect ortography",
|
| 28 |
+
"Kawaii": "kawaii-style illustration, pastel colors, cute rounded characters, big eyes.",
|
| 29 |
+
"Pictorial": "pictorial illustration, painted with a brush, gentle and calm atmosphere",
|
| 30 |
+
"Basic B&W": "black and white illustration, simple lines, no other colors allowed, like a coloring book.Absolutely only use black and white.",
|
| 31 |
+
"Cartoon": "cartoon-style illustration, exaggerated features, bright colors, playful mood",
|
| 32 |
+
"Soft Pastel": "Only use soft pastel colours, smooth textures, calming feeling"
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
VALID_VOICES = {"nova", "shimmer", "echo", "onyx", "fable", "alloy", "ash", "sage", "coral"}
|
| 36 |
+
|
| 37 |
+
# Initialize OpenAI client
|
| 38 |
+
def get_openai_client():
|
| 39 |
+
api_key = os.environ.get("OPENAI_API_KEY")
|
| 40 |
+
if not api_key:
|
| 41 |
+
raise ValueError("OPENAI_API_KEY not found in environment variables")
|
| 42 |
+
return OpenAI(api_key=api_key)
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
@mcp.tool()
|
| 46 |
+
def adapt_story(
|
| 47 |
+
story: str,
|
| 48 |
+
culture: str = "default",
|
| 49 |
+
age: str = "7",
|
| 50 |
+
gender: str = "female",
|
| 51 |
+
vibe: str = "Cartoon",
|
| 52 |
+
comfort_character: str = "Koala"
|
| 53 |
+
) -> str:
|
| 54 |
+
"""
|
| 55 |
+
Adapts a story for an autistic child based on culture, age, gender, and comfort character.
|
| 56 |
+
|
| 57 |
+
Args:
|
| 58 |
+
story: The original story text to adapt
|
| 59 |
+
culture: Cultural context (e.g., 'Latino', 'Roma', 'Muslim', 'default')
|
| 60 |
+
age: Child's age (e.g., '7')
|
| 61 |
+
gender: Gender identity (e.g., 'boy', 'girl', 'non-binary')
|
| 62 |
+
vibe: Illustration style for the story
|
| 63 |
+
comfort_character: Supportive character/animal (e.g., 'Koala', 'Robot')
|
| 64 |
+
|
| 65 |
+
Returns:
|
| 66 |
+
The adapted story in English
|
| 67 |
+
"""
|
| 68 |
+
prompt = (
|
| 69 |
+
f"Adapt the following story for an autistic child of age {age}, gender {gender}, from {culture} culture. "
|
| 70 |
+
f"Include '{comfort_character}' as a supportive friend, use the style '{vibe}', and make the story concrete, supportive, and easy to understand. "
|
| 71 |
+
"Avoid excessive emotion and exclamation marks. "
|
| 72 |
+
"Adapt language according to age, simpler sentences for smaller (ages 2 to 4) kids. "
|
| 73 |
+
"The absolute max length is 640 characters. "
|
| 74 |
+
"Do not mention any info related to format, like pages, panels or scenes. It is just one story. "
|
| 75 |
+
"Do not translate. Return only the adapted story in English."
|
| 76 |
+
f"\n\nStory:\n{story}"
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
client = get_openai_client()
|
| 80 |
+
response = client.chat.completions.create(
|
| 81 |
+
model="gpt-4o-mini",
|
| 82 |
+
messages=[{"role": "user", "content": prompt}]
|
| 83 |
+
)
|
| 84 |
+
|
| 85 |
+
adapted_story = response.choices[0].message.content.strip()
|
| 86 |
+
return adapted_story
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
@mcp.tool()
|
| 90 |
+
def translate_story(
|
| 91 |
+
story: str,
|
| 92 |
+
language: str = "en",
|
| 93 |
+
gender: str = "female"
|
| 94 |
+
) -> str:
|
| 95 |
+
"""
|
| 96 |
+
Translates a story to the selected language with proper grammatical gender.
|
| 97 |
+
|
| 98 |
+
Args:
|
| 99 |
+
story: The story text to translate
|
| 100 |
+
language: Target language (e.g., 'Spanish from Spain', 'en', 'fr')
|
| 101 |
+
gender: Gender for grammatical agreement (e.g., 'boy', 'girl', 'non-binary')
|
| 102 |
+
|
| 103 |
+
Returns:
|
| 104 |
+
The translated story
|
| 105 |
+
"""
|
| 106 |
+
if language.lower() == "en":
|
| 107 |
+
return story
|
| 108 |
+
|
| 109 |
+
prompt = (
|
| 110 |
+
f"Translate the following story to {language} and the corresponding regional or cultural variant if mentioned. "
|
| 111 |
+
f"Use correct grammatical gender and pronouns, adapted to the selected gender of the kid ('{gender}'). "
|
| 112 |
+
"If the story mentions an animal or character, translate its name to the most culturally and linguistically appropriate version for the target language and region. "
|
| 113 |
+
"For example, if the story mentions 'fox', translate it as 'zorro' for Spanish from Spain. "
|
| 114 |
+
"Return only the translated story, using the regional variety requested."
|
| 115 |
+
f"\n\nStory:\n{story}"
|
| 116 |
+
)
|
| 117 |
+
|
| 118 |
+
client = get_openai_client()
|
| 119 |
+
response = client.chat.completions.create(
|
| 120 |
+
model="gpt-4o-mini",
|
| 121 |
+
messages=[{"role": "user", "content": prompt}]
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
translated_story = response.choices[0].message.content.strip()
|
| 125 |
+
return translated_story
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
@mcp.tool()
|
| 129 |
+
def generate_voice(
|
| 130 |
+
story: str,
|
| 131 |
+
language: str = "en"
|
| 132 |
+
) -> bytes:
|
| 133 |
+
"""
|
| 134 |
+
Generates audio narration of the story using adaptive TTS with LLM-based voice selection.
|
| 135 |
+
|
| 136 |
+
Args:
|
| 137 |
+
story: The story text to narrate
|
| 138 |
+
language: Language for pronunciation (e.g., 'en', 'Spanish from Spain')
|
| 139 |
+
|
| 140 |
+
Returns:
|
| 141 |
+
Audio bytes in MP3 format
|
| 142 |
+
"""
|
| 143 |
+
client = get_openai_client()
|
| 144 |
+
|
| 145 |
+
# Use LLM to select the best voice
|
| 146 |
+
prompt = (
|
| 147 |
+
f"Read the following story aloud for a child in language '{language}'. "
|
| 148 |
+
"Select ONLY one of these OpenAI voices: nova, shimmer, echo, onyx, fable, alloy, ash, sage, coral. "
|
| 149 |
+
"Adapt pronunciation and prosody to match the regional variety (for example, Spanish from Spain vs. Mexico, English US vs. UK) with its specific phonemes, like Spanish Z. "
|
| 150 |
+
"Decide what is the right pronunciation choice in case there are foreign words, like talking about a corgi dog in Spanish. "
|
| 151 |
+
"If no specific voice is available, use the default for the language. "
|
| 152 |
+
"Make the speech clear, friendly, and easy to understand for autistic children. "
|
| 153 |
+
"Return only the name of the selected voice in the format: Voice: <voice_name>."
|
| 154 |
+
f"\n\nStory:\n{story}"
|
| 155 |
+
)
|
| 156 |
+
|
| 157 |
+
response = client.chat.completions.create(
|
| 158 |
+
model="gpt-4o-mini",
|
| 159 |
+
messages=[{"role": "user", "content": prompt}]
|
| 160 |
+
)
|
| 161 |
+
|
| 162 |
+
voice_decision = response.choices[0].message.content.strip()
|
| 163 |
+
print(f"[VOICE LLM DECISION]: {voice_decision}")
|
| 164 |
+
|
| 165 |
+
# Parse voice selection
|
| 166 |
+
voice = "nova" # Default
|
| 167 |
+
for line in voice_decision.splitlines():
|
| 168 |
+
if line.lower().startswith("voice:"):
|
| 169 |
+
candidate = line.split(":", 1)[1].strip().strip('"').strip("'")
|
| 170 |
+
if candidate in VALID_VOICES:
|
| 171 |
+
voice = candidate
|
| 172 |
+
else:
|
| 173 |
+
print(f"[VOICE WARNING] '{candidate}' is not a valid OpenAI voice. Using default: 'nova'.")
|
| 174 |
+
break
|
| 175 |
+
|
| 176 |
+
# Generate TTS
|
| 177 |
+
tts_response = client.audio.speech.create(
|
| 178 |
+
model="tts-1",
|
| 179 |
+
voice=voice,
|
| 180 |
+
input=story,
|
| 181 |
+
response_format="mp3"
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
return tts_response.content
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
@mcp.tool()
|
| 188 |
+
def generate_image(
|
| 189 |
+
scenario_name: str,
|
| 190 |
+
culture: str = "default",
|
| 191 |
+
age: str = "7",
|
| 192 |
+
gender: str = "female",
|
| 193 |
+
vibe: str = "Cartoon",
|
| 194 |
+
comfort_character: str = "Koala",
|
| 195 |
+
story_text: str = "",
|
| 196 |
+
language: str = "en"
|
| 197 |
+
) -> str:
|
| 198 |
+
"""
|
| 199 |
+
Generates an illustration based on a scenario and story parameters.
|
| 200 |
+
|
| 201 |
+
Args:
|
| 202 |
+
scenario_name: Name of the scenario template (e.g., 'first day school')
|
| 203 |
+
culture: Cultural context
|
| 204 |
+
age: Child's age
|
| 205 |
+
gender: Gender identity
|
| 206 |
+
vibe: Illustration style (Comic, Kawaii, Pictorial, Basic B&W, Cartoon, Soft Pastel)
|
| 207 |
+
comfort_character: Supportive character/animal
|
| 208 |
+
story_text: The story text to illustrate
|
| 209 |
+
language: Language for any text in the image
|
| 210 |
+
|
| 211 |
+
Returns:
|
| 212 |
+
Path to the generated image file
|
| 213 |
+
"""
|
| 214 |
+
# Load scenario template
|
| 215 |
+
# Handle both "scenario name" and "scenario_name" formats
|
| 216 |
+
scenario_file = scenario_name.replace(" ", "_").replace("-", "_")
|
| 217 |
+
filepath = TEMPLATES_DIR / f"{scenario_file}.json"
|
| 218 |
+
|
| 219 |
+
if not filepath.exists():
|
| 220 |
+
raise FileNotFoundError(f"Template not found: {scenario_file}. Available scenarios: {[p.stem for p in TEMPLATES_DIR.glob('*.json')]}")
|
| 221 |
+
|
| 222 |
+
template = json.loads(filepath.read_text())
|
| 223 |
+
vibe_desc = VIBE_DESCRIPTIONS.get(vibe, vibe)
|
| 224 |
+
|
| 225 |
+
# Translate story if needed
|
| 226 |
+
story_for_image = story_text
|
| 227 |
+
if language.lower() != "en" and story_text:
|
| 228 |
+
story_for_image = translate_story(story_text, language, gender)
|
| 229 |
+
|
| 230 |
+
# Create image prompt based on vibe
|
| 231 |
+
client = get_openai_client()
|
| 232 |
+
|
| 233 |
+
if vibe == "Comic":
|
| 234 |
+
image_prompt = (
|
| 235 |
+
f"STORY (in {language}): {story_for_image}\n"
|
| 236 |
+
f"INSTRUCTIONS (in English): FORMAT: Create a comic strip with exactly 4 panels. "
|
| 237 |
+
f"Each panel should have at most one speech bubble, and no more than 3 speech bubbles in total. "
|
| 238 |
+
f"Each speech bubble should contain only 1 to 3 words, in perfect {language} orthography. "
|
| 239 |
+
f"The comic must visually and textually reflect the story above. "
|
| 240 |
+
f"Show a child of age {age}, gender {gender}, and the comfort character {comfort_character} together in a safe, public, and friendly environment. "
|
| 241 |
+
f"Do not include any other animals. Use the story as inspiration for the actions and words in the comic. "
|
| 242 |
+
f"Context: {template['title']}. Culture: {culture}. Illustration style: {vibe_desc}."
|
| 243 |
+
)
|
| 244 |
+
else:
|
| 245 |
+
image_prompt = (
|
| 246 |
+
f"STORY (in {language}): {story_for_image}\n"
|
| 247 |
+
f"INSTRUCTIONS (in English): FORMAT: This is NOT a comic. Do NOT use panels, speech bubbles, or any text. Only draw a single illustration. "
|
| 248 |
+
f"Create an illustration in the style '{vibe}': {vibe_desc}. "
|
| 249 |
+
f"Show a child of age {age}, gender {gender}, and the comfort character {comfort_character} together in a safe, public and friendly environment. "
|
| 250 |
+
f"Do not include any other animals. Context: {template['title']}. Culture: {culture}. "
|
| 251 |
+
f"Only visual elements, no text. Key features: 5 visual features of the style. "
|
| 252 |
+
f"TONE AND MOOD: Gentle, supportive, calming. Friendly characters."
|
| 253 |
+
)
|
| 254 |
+
|
| 255 |
+
# Generate image
|
| 256 |
+
img = client.images.generate(
|
| 257 |
+
model="dall-e-3",
|
| 258 |
+
prompt=image_prompt,
|
| 259 |
+
size="1024x1024",
|
| 260 |
+
response_format="b64_json"
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
# Decode and save image
|
| 264 |
+
image_bytes = base64.b64decode(img.data[0].b64_json)
|
| 265 |
+
image = Image.open(io.BytesIO(image_bytes))
|
| 266 |
+
|
| 267 |
+
# Save to generated_images directory
|
| 268 |
+
image_filename = f"{scenario_file}_{vibe}_{language}.png"
|
| 269 |
+
image_path = GENERATED_IMAGES_DIR / image_filename
|
| 270 |
+
image.save(image_path)
|
| 271 |
+
|
| 272 |
+
return str(image_path)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
# Helper function to list available scenarios
|
| 276 |
+
@mcp.tool()
|
| 277 |
+
def list_scenarios() -> list[str]:
|
| 278 |
+
"""
|
| 279 |
+
Lists all available scenario templates.
|
| 280 |
+
|
| 281 |
+
Returns:
|
| 282 |
+
List of scenario names
|
| 283 |
+
"""
|
| 284 |
+
return [p.stem.replace("_", " ") for p in TEMPLATES_DIR.glob("*.json")]
|
uv.lock
CHANGED
|
@@ -115,6 +115,27 @@ wheels = [
|
|
| 115 |
{ url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" },
|
| 116 |
]
|
| 117 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
[[package]]
|
| 119 |
name = "brotli"
|
| 120 |
version = "1.2.0"
|
|
@@ -173,6 +194,15 @@ wheels = [
|
|
| 173 |
{ url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" },
|
| 174 |
]
|
| 175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 176 |
[[package]]
|
| 177 |
name = "certifi"
|
| 178 |
version = "2025.11.12"
|
|
@@ -380,6 +410,7 @@ version = "0.0.1"
|
|
| 380 |
source = { virtual = "." }
|
| 381 |
dependencies = [
|
| 382 |
{ name = "fastapi" },
|
|
|
|
| 383 |
{ name = "gradio" },
|
| 384 |
{ name = "mcp" },
|
| 385 |
{ name = "openai" },
|
|
@@ -392,6 +423,7 @@ dependencies = [
|
|
| 392 |
[package.metadata]
|
| 393 |
requires-dist = [
|
| 394 |
{ name = "fastapi", specifier = ">=0.122.0" },
|
|
|
|
| 395 |
{ name = "gradio", specifier = ">=6.0.1" },
|
| 396 |
{ name = "mcp", specifier = ">=1.22.0" },
|
| 397 |
{ name = "openai", specifier = ">=2.8.1" },
|
|
@@ -466,6 +498,32 @@ wheels = [
|
|
| 466 |
{ url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
|
| 467 |
]
|
| 468 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
[[package]]
|
| 470 |
name = "distro"
|
| 471 |
version = "1.9.0"
|
|
@@ -475,12 +533,52 @@ wheels = [
|
|
| 475 |
{ 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" },
|
| 476 |
]
|
| 477 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
[[package]]
|
| 479 |
name = "exceptiongroup"
|
| 480 |
version = "1.3.1"
|
| 481 |
source = { registry = "https://pypi.org/simple" }
|
| 482 |
dependencies = [
|
| 483 |
-
{ name = "typing-extensions", marker = "python_full_version < '3.
|
| 484 |
]
|
| 485 |
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
| 486 |
wheels = [
|
|
@@ -502,6 +600,32 @@ wheels = [
|
|
| 502 |
{ url = "https://files.pythonhosted.org/packages/7a/93/aa8072af4ff37b795f6bbf43dcaf61115f40f49935c7dbb180c9afc3f421/fastapi-0.122.0-py3-none-any.whl", hash = "sha256:a456e8915dfc6c8914a50d9651133bd47ec96d331c5b44600baa635538a30d67", size = 110671, upload-time = "2025-11-24T19:17:45.96Z" },
|
| 503 |
]
|
| 504 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 505 |
[[package]]
|
| 506 |
name = "ffmpy"
|
| 507 |
version = "1.0.0"
|
|
@@ -823,6 +947,21 @@ wheels = [
|
|
| 823 |
{ url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
|
| 824 |
]
|
| 825 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 826 |
[[package]]
|
| 827 |
name = "jsonschema-specifications"
|
| 828 |
version = "2025.9.1"
|
|
@@ -1136,6 +1275,18 @@ wheels = [
|
|
| 1136 |
{ url = "https://files.pythonhosted.org/packages/55/4f/dbc0c124c40cb390508a82770fb9f6e3ed162560181a85089191a851c59a/openai-2.8.1-py3-none-any.whl", hash = "sha256:c6c3b5a04994734386e8dad3c00a393f56d3b68a27cd2e8acae91a59e4122463", size = 1022688, upload-time = "2025-11-17T22:39:57.675Z" },
|
| 1137 |
]
|
| 1138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1139 |
[[package]]
|
| 1140 |
name = "orjson"
|
| 1141 |
version = "3.11.4"
|
|
@@ -1288,6 +1439,24 @@ wheels = [
|
|
| 1288 |
{ 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" },
|
| 1289 |
]
|
| 1290 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1291 |
[[package]]
|
| 1292 |
name = "pillow"
|
| 1293 |
version = "12.0.0"
|
|
@@ -1386,6 +1555,50 @@ wheels = [
|
|
| 1386 |
{ url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" },
|
| 1387 |
]
|
| 1388 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1389 |
[[package]]
|
| 1390 |
name = "pycparser"
|
| 1391 |
version = "2.23"
|
|
@@ -1410,6 +1623,11 @@ wheels = [
|
|
| 1410 |
{ url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" },
|
| 1411 |
]
|
| 1412 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1413 |
[[package]]
|
| 1414 |
name = "pydantic-core"
|
| 1415 |
version = "2.41.5"
|
|
@@ -1574,6 +1792,15 @@ crypto = [
|
|
| 1574 |
{ name = "cryptography" },
|
| 1575 |
]
|
| 1576 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1577 |
[[package]]
|
| 1578 |
name = "python-dateutil"
|
| 1579 |
version = "2.9.0.post0"
|
|
@@ -1701,16 +1928,16 @@ wheels = [
|
|
| 1701 |
|
| 1702 |
[[package]]
|
| 1703 |
name = "referencing"
|
| 1704 |
-
version = "0.
|
| 1705 |
source = { registry = "https://pypi.org/simple" }
|
| 1706 |
dependencies = [
|
| 1707 |
{ name = "attrs" },
|
| 1708 |
{ name = "rpds-py" },
|
| 1709 |
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 1710 |
]
|
| 1711 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 1712 |
wheels = [
|
| 1713 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 1714 |
]
|
| 1715 |
|
| 1716 |
[[package]]
|
|
@@ -1741,6 +1968,19 @@ wheels = [
|
|
| 1741 |
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
| 1742 |
]
|
| 1743 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1744 |
[[package]]
|
| 1745 |
name = "rpds-py"
|
| 1746 |
version = "0.29.0"
|
|
@@ -1936,6 +2176,55 @@ wheels = [
|
|
| 1936 |
{ 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" },
|
| 1937 |
]
|
| 1938 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1939 |
[[package]]
|
| 1940 |
name = "tomlkit"
|
| 1941 |
version = "0.13.3"
|
|
@@ -2037,3 +2326,62 @@ sdist = { url = "https://files.pythonhosted.org/packages/cb/ce/f06b84e2697fef468
|
|
| 2037 |
wheels = [
|
| 2038 |
{ url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" },
|
| 2039 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 115 |
{ url = "https://files.pythonhosted.org/packages/f6/22/91616fe707a5c5510de2cac9b046a30defe7007ba8a0c04f9c08f27df312/audioop_lts-0.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:b492c3b040153e68b9fdaff5913305aaaba5bb433d8a7f73d5cf6a64ed3cc1dd", size = 25206, upload-time = "2025-08-05T16:43:16.444Z" },
|
| 116 |
]
|
| 117 |
|
| 118 |
+
[[package]]
|
| 119 |
+
name = "authlib"
|
| 120 |
+
version = "1.6.5"
|
| 121 |
+
source = { registry = "https://pypi.org/simple" }
|
| 122 |
+
dependencies = [
|
| 123 |
+
{ name = "cryptography" },
|
| 124 |
+
]
|
| 125 |
+
sdist = { url = "https://files.pythonhosted.org/packages/cd/3f/1d3bbd0bf23bdd99276d4def22f29c27a914067b4cf66f753ff9b8bbd0f3/authlib-1.6.5.tar.gz", hash = "sha256:6aaf9c79b7cc96c900f0b284061691c5d4e61221640a948fe690b556a6d6d10b", size = 164553, upload-time = "2025-10-02T13:36:09.489Z" }
|
| 126 |
+
wheels = [
|
| 127 |
+
{ url = "https://files.pythonhosted.org/packages/f8/aa/5082412d1ee302e9e7d80b6949bc4d2a8fa1149aaab610c5fc24709605d6/authlib-1.6.5-py2.py3-none-any.whl", hash = "sha256:3e0e0507807f842b02175507bdee8957a1d5707fd4afb17c32fb43fee90b6e3a", size = 243608, upload-time = "2025-10-02T13:36:07.637Z" },
|
| 128 |
+
]
|
| 129 |
+
|
| 130 |
+
[[package]]
|
| 131 |
+
name = "beartype"
|
| 132 |
+
version = "0.22.8"
|
| 133 |
+
source = { registry = "https://pypi.org/simple" }
|
| 134 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8c/1d/794ae2acaa67c8b216d91d5919da2606c2bb14086849ffde7f5555f3a3a5/beartype-0.22.8.tar.gz", hash = "sha256:b19b21c9359722ee3f7cc433f063b3e13997b27ae8226551ea5062e621f61165", size = 1602262, upload-time = "2025-12-03T05:11:10.766Z" }
|
| 135 |
+
wheels = [
|
| 136 |
+
{ url = "https://files.pythonhosted.org/packages/14/2a/fbcbf5a025d3e71ddafad7efd43e34ec4362f4d523c3c471b457148fb211/beartype-0.22.8-py3-none-any.whl", hash = "sha256:b832882d04e41a4097bab9f63e6992bc6de58c414ee84cba9b45b67314f5ab2e", size = 1331895, upload-time = "2025-12-03T05:11:08.373Z" },
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
[[package]]
|
| 140 |
name = "brotli"
|
| 141 |
version = "1.2.0"
|
|
|
|
| 194 |
{ url = "https://files.pythonhosted.org/packages/f5/10/56978295c14794b2c12007b07f3e41ba26acda9257457d7085b0bb3bb90c/brotli-1.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:e7c0af964e0b4e3412a0ebf341ea26ec767fa0b4cf81abb5e897c9338b5ad6a3", size = 375639, upload-time = "2025-11-05T18:38:55.67Z" },
|
| 195 |
]
|
| 196 |
|
| 197 |
+
[[package]]
|
| 198 |
+
name = "cachetools"
|
| 199 |
+
version = "6.2.2"
|
| 200 |
+
source = { registry = "https://pypi.org/simple" }
|
| 201 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fb/44/ca1675be2a83aeee1886ab745b28cda92093066590233cc501890eb8417a/cachetools-6.2.2.tar.gz", hash = "sha256:8e6d266b25e539df852251cfd6f990b4bc3a141db73b939058d809ebd2590fc6", size = 31571, upload-time = "2025-11-13T17:42:51.465Z" }
|
| 202 |
+
wheels = [
|
| 203 |
+
{ url = "https://files.pythonhosted.org/packages/e6/46/eb6eca305c77a4489affe1c5d8f4cae82f285d9addd8de4ec084a7184221/cachetools-6.2.2-py3-none-any.whl", hash = "sha256:6c09c98183bf58560c97b2abfcedcbaf6a896a490f534b031b661d3723b45ace", size = 11503, upload-time = "2025-11-13T17:42:50.232Z" },
|
| 204 |
+
]
|
| 205 |
+
|
| 206 |
[[package]]
|
| 207 |
name = "certifi"
|
| 208 |
version = "2025.11.12"
|
|
|
|
| 410 |
source = { virtual = "." }
|
| 411 |
dependencies = [
|
| 412 |
{ name = "fastapi" },
|
| 413 |
+
{ name = "fastmcp" },
|
| 414 |
{ name = "gradio" },
|
| 415 |
{ name = "mcp" },
|
| 416 |
{ name = "openai" },
|
|
|
|
| 423 |
[package.metadata]
|
| 424 |
requires-dist = [
|
| 425 |
{ name = "fastapi", specifier = ">=0.122.0" },
|
| 426 |
+
{ name = "fastmcp", specifier = ">=2.13.2" },
|
| 427 |
{ name = "gradio", specifier = ">=6.0.1" },
|
| 428 |
{ name = "mcp", specifier = ">=1.22.0" },
|
| 429 |
{ name = "openai", specifier = ">=2.8.1" },
|
|
|
|
| 498 |
{ url = "https://files.pythonhosted.org/packages/0d/c3/e90f4a4feae6410f914f8ebac129b9ae7a8c92eb60a638012dde42030a9d/cryptography-46.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6b5063083824e5509fdba180721d55909ffacccc8adbec85268b48439423d78c", size = 3438528, upload-time = "2025-10-15T23:18:26.227Z" },
|
| 499 |
]
|
| 500 |
|
| 501 |
+
[[package]]
|
| 502 |
+
name = "cyclopts"
|
| 503 |
+
version = "4.3.0"
|
| 504 |
+
source = { registry = "https://pypi.org/simple" }
|
| 505 |
+
dependencies = [
|
| 506 |
+
{ name = "attrs" },
|
| 507 |
+
{ name = "docstring-parser" },
|
| 508 |
+
{ name = "rich" },
|
| 509 |
+
{ name = "rich-rst" },
|
| 510 |
+
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
| 511 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
|
| 512 |
+
]
|
| 513 |
+
sdist = { url = "https://files.pythonhosted.org/packages/1b/0f/fe026df2ab8301e30a2b0bd425ff1462ad858fd4f991c1ac0389c2059c24/cyclopts-4.3.0.tar.gz", hash = "sha256:e95179cd0a959ce250ecfb2f0262a5996a92c1f9467bccad2f3d829e6833cef5", size = 151411, upload-time = "2025-11-25T02:59:33.572Z" }
|
| 514 |
+
wheels = [
|
| 515 |
+
{ url = "https://files.pythonhosted.org/packages/7a/e8/77a231ae531cf38765b75ddf27dae28bb5f70b41d8bb4f15ce1650e93f57/cyclopts-4.3.0-py3-none-any.whl", hash = "sha256:91a30b69faf128ada7cfeaefd7d9649dc222e8b2a8697f1fc99e4ee7b7ca44f3", size = 187184, upload-time = "2025-11-25T02:59:32.21Z" },
|
| 516 |
+
]
|
| 517 |
+
|
| 518 |
+
[[package]]
|
| 519 |
+
name = "diskcache"
|
| 520 |
+
version = "5.6.3"
|
| 521 |
+
source = { registry = "https://pypi.org/simple" }
|
| 522 |
+
sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" }
|
| 523 |
+
wheels = [
|
| 524 |
+
{ 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" },
|
| 525 |
+
]
|
| 526 |
+
|
| 527 |
[[package]]
|
| 528 |
name = "distro"
|
| 529 |
version = "1.9.0"
|
|
|
|
| 533 |
{ 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" },
|
| 534 |
]
|
| 535 |
|
| 536 |
+
[[package]]
|
| 537 |
+
name = "dnspython"
|
| 538 |
+
version = "2.8.0"
|
| 539 |
+
source = { registry = "https://pypi.org/simple" }
|
| 540 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8c/8b/57666417c0f90f08bcafa776861060426765fdb422eb10212086fb811d26/dnspython-2.8.0.tar.gz", hash = "sha256:181d3c6996452cb1189c4046c61599b84a5a86e099562ffde77d26984ff26d0f", size = 368251, upload-time = "2025-09-07T18:58:00.022Z" }
|
| 541 |
+
wheels = [
|
| 542 |
+
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
|
| 543 |
+
]
|
| 544 |
+
|
| 545 |
+
[[package]]
|
| 546 |
+
name = "docstring-parser"
|
| 547 |
+
version = "0.17.0"
|
| 548 |
+
source = { registry = "https://pypi.org/simple" }
|
| 549 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
|
| 550 |
+
wheels = [
|
| 551 |
+
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
|
| 552 |
+
]
|
| 553 |
+
|
| 554 |
+
[[package]]
|
| 555 |
+
name = "docutils"
|
| 556 |
+
version = "0.22.3"
|
| 557 |
+
source = { registry = "https://pypi.org/simple" }
|
| 558 |
+
sdist = { url = "https://files.pythonhosted.org/packages/d9/02/111134bfeb6e6c7ac4c74594e39a59f6c0195dc4846afbeac3cba60f1927/docutils-0.22.3.tar.gz", hash = "sha256:21486ae730e4ca9f622677b1412b879af1791efcfba517e4c6f60be543fc8cdd", size = 2290153, upload-time = "2025-11-06T02:35:55.655Z" }
|
| 559 |
+
wheels = [
|
| 560 |
+
{ url = "https://files.pythonhosted.org/packages/11/a8/c6a4b901d17399c77cd81fb001ce8961e9f5e04d3daf27e8925cb012e163/docutils-0.22.3-py3-none-any.whl", hash = "sha256:bd772e4aca73aff037958d44f2be5229ded4c09927fcf8690c577b66234d6ceb", size = 633032, upload-time = "2025-11-06T02:35:52.391Z" },
|
| 561 |
+
]
|
| 562 |
+
|
| 563 |
+
[[package]]
|
| 564 |
+
name = "email-validator"
|
| 565 |
+
version = "2.3.0"
|
| 566 |
+
source = { registry = "https://pypi.org/simple" }
|
| 567 |
+
dependencies = [
|
| 568 |
+
{ name = "dnspython" },
|
| 569 |
+
{ name = "idna" },
|
| 570 |
+
]
|
| 571 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f5/22/900cb125c76b7aaa450ce02fd727f452243f2e91a61af068b40adba60ea9/email_validator-2.3.0.tar.gz", hash = "sha256:9fc05c37f2f6cf439ff414f8fc46d917929974a82244c20eb10231ba60c54426", size = 51238, upload-time = "2025-08-26T13:09:06.831Z" }
|
| 572 |
+
wheels = [
|
| 573 |
+
{ url = "https://files.pythonhosted.org/packages/de/15/545e2b6cf2e3be84bc1ed85613edd75b8aea69807a71c26f4ca6a9258e82/email_validator-2.3.0-py3-none-any.whl", hash = "sha256:80f13f623413e6b197ae73bb10bf4eb0908faf509ad8362c5edeb0be7fd450b4", size = 35604, upload-time = "2025-08-26T13:09:05.858Z" },
|
| 574 |
+
]
|
| 575 |
+
|
| 576 |
[[package]]
|
| 577 |
name = "exceptiongroup"
|
| 578 |
version = "1.3.1"
|
| 579 |
source = { registry = "https://pypi.org/simple" }
|
| 580 |
dependencies = [
|
| 581 |
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 582 |
]
|
| 583 |
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
| 584 |
wheels = [
|
|
|
|
| 600 |
{ url = "https://files.pythonhosted.org/packages/7a/93/aa8072af4ff37b795f6bbf43dcaf61115f40f49935c7dbb180c9afc3f421/fastapi-0.122.0-py3-none-any.whl", hash = "sha256:a456e8915dfc6c8914a50d9651133bd47ec96d331c5b44600baa635538a30d67", size = 110671, upload-time = "2025-11-24T19:17:45.96Z" },
|
| 601 |
]
|
| 602 |
|
| 603 |
+
[[package]]
|
| 604 |
+
name = "fastmcp"
|
| 605 |
+
version = "2.13.2"
|
| 606 |
+
source = { registry = "https://pypi.org/simple" }
|
| 607 |
+
dependencies = [
|
| 608 |
+
{ name = "authlib" },
|
| 609 |
+
{ name = "cyclopts" },
|
| 610 |
+
{ name = "exceptiongroup" },
|
| 611 |
+
{ name = "httpx" },
|
| 612 |
+
{ name = "jsonschema-path" },
|
| 613 |
+
{ name = "mcp" },
|
| 614 |
+
{ name = "openapi-pydantic" },
|
| 615 |
+
{ name = "platformdirs" },
|
| 616 |
+
{ name = "py-key-value-aio", extra = ["disk", "memory"] },
|
| 617 |
+
{ name = "pydantic", extra = ["email"] },
|
| 618 |
+
{ name = "pyperclip" },
|
| 619 |
+
{ name = "python-dotenv" },
|
| 620 |
+
{ name = "rich" },
|
| 621 |
+
{ name = "uvicorn" },
|
| 622 |
+
{ name = "websockets" },
|
| 623 |
+
]
|
| 624 |
+
sdist = { url = "https://files.pythonhosted.org/packages/c8/7a/4c6375a56f7458a4a6af62f4c4838a2c957a665cf5edad26fe95395666f1/fastmcp-2.13.2.tar.gz", hash = "sha256:2a206401a6579fea621974162674beba85b467ad72c70c1a3752a31951dff7f0", size = 8185950, upload-time = "2025-12-01T18:48:16.834Z" }
|
| 625 |
+
wheels = [
|
| 626 |
+
{ url = "https://files.pythonhosted.org/packages/e5/4b/73c68b0ae9e587f20c5aa13ba5bed9be2bb9248a598555dafcf17df87f70/fastmcp-2.13.2-py3-none-any.whl", hash = "sha256:300c59eb970c235bb9d0575883322922e4f2e2468a3d45e90cbfd6b23b7be245", size = 385643, upload-time = "2025-12-01T18:48:18.515Z" },
|
| 627 |
+
]
|
| 628 |
+
|
| 629 |
[[package]]
|
| 630 |
name = "ffmpy"
|
| 631 |
version = "1.0.0"
|
|
|
|
| 947 |
{ url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
|
| 948 |
]
|
| 949 |
|
| 950 |
+
[[package]]
|
| 951 |
+
name = "jsonschema-path"
|
| 952 |
+
version = "0.3.4"
|
| 953 |
+
source = { registry = "https://pypi.org/simple" }
|
| 954 |
+
dependencies = [
|
| 955 |
+
{ name = "pathable" },
|
| 956 |
+
{ name = "pyyaml" },
|
| 957 |
+
{ name = "referencing" },
|
| 958 |
+
{ name = "requests" },
|
| 959 |
+
]
|
| 960 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" }
|
| 961 |
+
wheels = [
|
| 962 |
+
{ url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810, upload-time = "2025-01-24T14:33:14.652Z" },
|
| 963 |
+
]
|
| 964 |
+
|
| 965 |
[[package]]
|
| 966 |
name = "jsonschema-specifications"
|
| 967 |
version = "2025.9.1"
|
|
|
|
| 1275 |
{ url = "https://files.pythonhosted.org/packages/55/4f/dbc0c124c40cb390508a82770fb9f6e3ed162560181a85089191a851c59a/openai-2.8.1-py3-none-any.whl", hash = "sha256:c6c3b5a04994734386e8dad3c00a393f56d3b68a27cd2e8acae91a59e4122463", size = 1022688, upload-time = "2025-11-17T22:39:57.675Z" },
|
| 1276 |
]
|
| 1277 |
|
| 1278 |
+
[[package]]
|
| 1279 |
+
name = "openapi-pydantic"
|
| 1280 |
+
version = "0.5.1"
|
| 1281 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1282 |
+
dependencies = [
|
| 1283 |
+
{ name = "pydantic" },
|
| 1284 |
+
]
|
| 1285 |
+
sdist = { url = "https://files.pythonhosted.org/packages/02/2e/58d83848dd1a79cb92ed8e63f6ba901ca282c5f09d04af9423ec26c56fd7/openapi_pydantic-0.5.1.tar.gz", hash = "sha256:ff6835af6bde7a459fb93eb93bb92b8749b754fc6e51b2f1590a19dc3005ee0d", size = 60892, upload-time = "2025-01-08T19:29:27.083Z" }
|
| 1286 |
+
wheels = [
|
| 1287 |
+
{ url = "https://files.pythonhosted.org/packages/12/cf/03675d8bd8ecbf4445504d8071adab19f5f993676795708e36402ab38263/openapi_pydantic-0.5.1-py3-none-any.whl", hash = "sha256:a3a09ef4586f5bd760a8df7f43028b60cafb6d9f61de2acba9574766255ab146", size = 96381, upload-time = "2025-01-08T19:29:25.275Z" },
|
| 1288 |
+
]
|
| 1289 |
+
|
| 1290 |
[[package]]
|
| 1291 |
name = "orjson"
|
| 1292 |
version = "3.11.4"
|
|
|
|
| 1439 |
{ 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" },
|
| 1440 |
]
|
| 1441 |
|
| 1442 |
+
[[package]]
|
| 1443 |
+
name = "pathable"
|
| 1444 |
+
version = "0.4.4"
|
| 1445 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1446 |
+
sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124, upload-time = "2025-01-10T18:43:13.247Z" }
|
| 1447 |
+
wheels = [
|
| 1448 |
+
{ url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" },
|
| 1449 |
+
]
|
| 1450 |
+
|
| 1451 |
+
[[package]]
|
| 1452 |
+
name = "pathvalidate"
|
| 1453 |
+
version = "3.3.1"
|
| 1454 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1455 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" }
|
| 1456 |
+
wheels = [
|
| 1457 |
+
{ 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" },
|
| 1458 |
+
]
|
| 1459 |
+
|
| 1460 |
[[package]]
|
| 1461 |
name = "pillow"
|
| 1462 |
version = "12.0.0"
|
|
|
|
| 1555 |
{ url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" },
|
| 1556 |
]
|
| 1557 |
|
| 1558 |
+
[[package]]
|
| 1559 |
+
name = "platformdirs"
|
| 1560 |
+
version = "4.5.0"
|
| 1561 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1562 |
+
sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" }
|
| 1563 |
+
wheels = [
|
| 1564 |
+
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
| 1565 |
+
]
|
| 1566 |
+
|
| 1567 |
+
[[package]]
|
| 1568 |
+
name = "py-key-value-aio"
|
| 1569 |
+
version = "0.3.0"
|
| 1570 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1571 |
+
dependencies = [
|
| 1572 |
+
{ name = "beartype" },
|
| 1573 |
+
{ name = "py-key-value-shared" },
|
| 1574 |
+
]
|
| 1575 |
+
sdist = { url = "https://files.pythonhosted.org/packages/93/ce/3136b771dddf5ac905cc193b461eb67967cf3979688c6696e1f2cdcde7ea/py_key_value_aio-0.3.0.tar.gz", hash = "sha256:858e852fcf6d696d231266da66042d3355a7f9871650415feef9fca7a6cd4155", size = 50801, upload-time = "2025-11-17T16:50:04.711Z" }
|
| 1576 |
+
wheels = [
|
| 1577 |
+
{ url = "https://files.pythonhosted.org/packages/99/10/72f6f213b8f0bce36eff21fda0a13271834e9eeff7f9609b01afdc253c79/py_key_value_aio-0.3.0-py3-none-any.whl", hash = "sha256:1c781915766078bfd608daa769fefb97e65d1d73746a3dfb640460e322071b64", size = 96342, upload-time = "2025-11-17T16:50:03.801Z" },
|
| 1578 |
+
]
|
| 1579 |
+
|
| 1580 |
+
[package.optional-dependencies]
|
| 1581 |
+
disk = [
|
| 1582 |
+
{ name = "diskcache" },
|
| 1583 |
+
{ name = "pathvalidate" },
|
| 1584 |
+
]
|
| 1585 |
+
memory = [
|
| 1586 |
+
{ name = "cachetools" },
|
| 1587 |
+
]
|
| 1588 |
+
|
| 1589 |
+
[[package]]
|
| 1590 |
+
name = "py-key-value-shared"
|
| 1591 |
+
version = "0.3.0"
|
| 1592 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1593 |
+
dependencies = [
|
| 1594 |
+
{ name = "beartype" },
|
| 1595 |
+
{ name = "typing-extensions" },
|
| 1596 |
+
]
|
| 1597 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7b/e4/1971dfc4620a3a15b4579fe99e024f5edd6e0967a71154771a059daff4db/py_key_value_shared-0.3.0.tar.gz", hash = "sha256:8fdd786cf96c3e900102945f92aa1473138ebe960ef49da1c833790160c28a4b", size = 11666, upload-time = "2025-11-17T16:50:06.849Z" }
|
| 1598 |
+
wheels = [
|
| 1599 |
+
{ 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" },
|
| 1600 |
+
]
|
| 1601 |
+
|
| 1602 |
[[package]]
|
| 1603 |
name = "pycparser"
|
| 1604 |
version = "2.23"
|
|
|
|
| 1623 |
{ url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" },
|
| 1624 |
]
|
| 1625 |
|
| 1626 |
+
[package.optional-dependencies]
|
| 1627 |
+
email = [
|
| 1628 |
+
{ name = "email-validator" },
|
| 1629 |
+
]
|
| 1630 |
+
|
| 1631 |
[[package]]
|
| 1632 |
name = "pydantic-core"
|
| 1633 |
version = "2.41.5"
|
|
|
|
| 1792 |
{ name = "cryptography" },
|
| 1793 |
]
|
| 1794 |
|
| 1795 |
+
[[package]]
|
| 1796 |
+
name = "pyperclip"
|
| 1797 |
+
version = "1.11.0"
|
| 1798 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1799 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" }
|
| 1800 |
+
wheels = [
|
| 1801 |
+
{ url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" },
|
| 1802 |
+
]
|
| 1803 |
+
|
| 1804 |
[[package]]
|
| 1805 |
name = "python-dateutil"
|
| 1806 |
version = "2.9.0.post0"
|
|
|
|
| 1928 |
|
| 1929 |
[[package]]
|
| 1930 |
name = "referencing"
|
| 1931 |
+
version = "0.36.2"
|
| 1932 |
source = { registry = "https://pypi.org/simple" }
|
| 1933 |
dependencies = [
|
| 1934 |
{ name = "attrs" },
|
| 1935 |
{ name = "rpds-py" },
|
| 1936 |
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
| 1937 |
]
|
| 1938 |
+
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
|
| 1939 |
wheels = [
|
| 1940 |
+
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
|
| 1941 |
]
|
| 1942 |
|
| 1943 |
[[package]]
|
|
|
|
| 1968 |
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
| 1969 |
]
|
| 1970 |
|
| 1971 |
+
[[package]]
|
| 1972 |
+
name = "rich-rst"
|
| 1973 |
+
version = "1.3.2"
|
| 1974 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1975 |
+
dependencies = [
|
| 1976 |
+
{ name = "docutils" },
|
| 1977 |
+
{ name = "rich" },
|
| 1978 |
+
]
|
| 1979 |
+
sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" }
|
| 1980 |
+
wheels = [
|
| 1981 |
+
{ url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" },
|
| 1982 |
+
]
|
| 1983 |
+
|
| 1984 |
[[package]]
|
| 1985 |
name = "rpds-py"
|
| 1986 |
version = "0.29.0"
|
|
|
|
| 2176 |
{ 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" },
|
| 2177 |
]
|
| 2178 |
|
| 2179 |
+
[[package]]
|
| 2180 |
+
name = "tomli"
|
| 2181 |
+
version = "2.3.0"
|
| 2182 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2183 |
+
sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" }
|
| 2184 |
+
wheels = [
|
| 2185 |
+
{ url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" },
|
| 2186 |
+
{ url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" },
|
| 2187 |
+
{ url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" },
|
| 2188 |
+
{ url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" },
|
| 2189 |
+
{ url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" },
|
| 2190 |
+
{ url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" },
|
| 2191 |
+
{ url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" },
|
| 2192 |
+
{ url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" },
|
| 2193 |
+
{ url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" },
|
| 2194 |
+
{ url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" },
|
| 2195 |
+
{ url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" },
|
| 2196 |
+
{ url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" },
|
| 2197 |
+
{ url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" },
|
| 2198 |
+
{ url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" },
|
| 2199 |
+
{ url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" },
|
| 2200 |
+
{ url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" },
|
| 2201 |
+
{ url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" },
|
| 2202 |
+
{ url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" },
|
| 2203 |
+
{ url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" },
|
| 2204 |
+
{ url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" },
|
| 2205 |
+
{ url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" },
|
| 2206 |
+
{ url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" },
|
| 2207 |
+
{ url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" },
|
| 2208 |
+
{ url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" },
|
| 2209 |
+
{ url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" },
|
| 2210 |
+
{ url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" },
|
| 2211 |
+
{ url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" },
|
| 2212 |
+
{ url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" },
|
| 2213 |
+
{ url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" },
|
| 2214 |
+
{ url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" },
|
| 2215 |
+
{ url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" },
|
| 2216 |
+
{ url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" },
|
| 2217 |
+
{ url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" },
|
| 2218 |
+
{ url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" },
|
| 2219 |
+
{ url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" },
|
| 2220 |
+
{ url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" },
|
| 2221 |
+
{ url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" },
|
| 2222 |
+
{ url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" },
|
| 2223 |
+
{ url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" },
|
| 2224 |
+
{ url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" },
|
| 2225 |
+
{ 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" },
|
| 2226 |
+
]
|
| 2227 |
+
|
| 2228 |
[[package]]
|
| 2229 |
name = "tomlkit"
|
| 2230 |
version = "0.13.3"
|
|
|
|
| 2326 |
wheels = [
|
| 2327 |
{ url = "https://files.pythonhosted.org/packages/ee/d9/d88e73ca598f4f6ff671fb5fde8a32925c2e08a637303a1d12883c7305fa/uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02", size = 68109, upload-time = "2025-10-18T13:46:42.958Z" },
|
| 2328 |
]
|
| 2329 |
+
|
| 2330 |
+
[[package]]
|
| 2331 |
+
name = "websockets"
|
| 2332 |
+
version = "15.0.1"
|
| 2333 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2334 |
+
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" }
|
| 2335 |
+
wheels = [
|
| 2336 |
+
{ url = "https://files.pythonhosted.org/packages/1e/da/6462a9f510c0c49837bbc9345aca92d767a56c1fb2939e1579df1e1cdcf7/websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b", size = 175423, upload-time = "2025-03-05T20:01:35.363Z" },
|
| 2337 |
+
{ url = "https://files.pythonhosted.org/packages/1c/9f/9d11c1a4eb046a9e106483b9ff69bce7ac880443f00e5ce64261b47b07e7/websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205", size = 173080, upload-time = "2025-03-05T20:01:37.304Z" },
|
| 2338 |
+
{ url = "https://files.pythonhosted.org/packages/d5/4f/b462242432d93ea45f297b6179c7333dd0402b855a912a04e7fc61c0d71f/websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a", size = 173329, upload-time = "2025-03-05T20:01:39.668Z" },
|
| 2339 |
+
{ url = "https://files.pythonhosted.org/packages/6e/0c/6afa1f4644d7ed50284ac59cc70ef8abd44ccf7d45850d989ea7310538d0/websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e", size = 182312, upload-time = "2025-03-05T20:01:41.815Z" },
|
| 2340 |
+
{ url = "https://files.pythonhosted.org/packages/dd/d4/ffc8bd1350b229ca7a4db2a3e1c482cf87cea1baccd0ef3e72bc720caeec/websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf", size = 181319, upload-time = "2025-03-05T20:01:43.967Z" },
|
| 2341 |
+
{ url = "https://files.pythonhosted.org/packages/97/3a/5323a6bb94917af13bbb34009fac01e55c51dfde354f63692bf2533ffbc2/websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb", size = 181631, upload-time = "2025-03-05T20:01:46.104Z" },
|
| 2342 |
+
{ url = "https://files.pythonhosted.org/packages/a6/cc/1aeb0f7cee59ef065724041bb7ed667b6ab1eeffe5141696cccec2687b66/websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d", size = 182016, upload-time = "2025-03-05T20:01:47.603Z" },
|
| 2343 |
+
{ url = "https://files.pythonhosted.org/packages/79/f9/c86f8f7af208e4161a7f7e02774e9d0a81c632ae76db2ff22549e1718a51/websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9", size = 181426, upload-time = "2025-03-05T20:01:48.949Z" },
|
| 2344 |
+
{ url = "https://files.pythonhosted.org/packages/c7/b9/828b0bc6753db905b91df6ae477c0b14a141090df64fb17f8a9d7e3516cf/websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c", size = 181360, upload-time = "2025-03-05T20:01:50.938Z" },
|
| 2345 |
+
{ url = "https://files.pythonhosted.org/packages/89/fb/250f5533ec468ba6327055b7d98b9df056fb1ce623b8b6aaafb30b55d02e/websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256", size = 176388, upload-time = "2025-03-05T20:01:52.213Z" },
|
| 2346 |
+
{ url = "https://files.pythonhosted.org/packages/1c/46/aca7082012768bb98e5608f01658ff3ac8437e563eca41cf068bd5849a5e/websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41", size = 176830, upload-time = "2025-03-05T20:01:53.922Z" },
|
| 2347 |
+
{ url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" },
|
| 2348 |
+
{ url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" },
|
| 2349 |
+
{ url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" },
|
| 2350 |
+
{ url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" },
|
| 2351 |
+
{ url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" },
|
| 2352 |
+
{ url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" },
|
| 2353 |
+
{ url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" },
|
| 2354 |
+
{ url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" },
|
| 2355 |
+
{ url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" },
|
| 2356 |
+
{ url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" },
|
| 2357 |
+
{ url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" },
|
| 2358 |
+
{ url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" },
|
| 2359 |
+
{ url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" },
|
| 2360 |
+
{ url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" },
|
| 2361 |
+
{ url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" },
|
| 2362 |
+
{ url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" },
|
| 2363 |
+
{ url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" },
|
| 2364 |
+
{ url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" },
|
| 2365 |
+
{ url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" },
|
| 2366 |
+
{ url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" },
|
| 2367 |
+
{ url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" },
|
| 2368 |
+
{ url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" },
|
| 2369 |
+
{ url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" },
|
| 2370 |
+
{ url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" },
|
| 2371 |
+
{ url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" },
|
| 2372 |
+
{ url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" },
|
| 2373 |
+
{ url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" },
|
| 2374 |
+
{ url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" },
|
| 2375 |
+
{ url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" },
|
| 2376 |
+
{ url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" },
|
| 2377 |
+
{ url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" },
|
| 2378 |
+
{ url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" },
|
| 2379 |
+
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" },
|
| 2380 |
+
{ url = "https://files.pythonhosted.org/packages/02/9e/d40f779fa16f74d3468357197af8d6ad07e7c5a27ea1ca74ceb38986f77a/websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3", size = 173109, upload-time = "2025-03-05T20:03:17.769Z" },
|
| 2381 |
+
{ url = "https://files.pythonhosted.org/packages/bc/cd/5b887b8585a593073fd92f7c23ecd3985cd2c3175025a91b0d69b0551372/websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1", size = 173343, upload-time = "2025-03-05T20:03:19.094Z" },
|
| 2382 |
+
{ url = "https://files.pythonhosted.org/packages/fe/ae/d34f7556890341e900a95acf4886833646306269f899d58ad62f588bf410/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475", size = 174599, upload-time = "2025-03-05T20:03:21.1Z" },
|
| 2383 |
+
{ url = "https://files.pythonhosted.org/packages/71/e6/5fd43993a87db364ec60fc1d608273a1a465c0caba69176dd160e197ce42/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9", size = 174207, upload-time = "2025-03-05T20:03:23.221Z" },
|
| 2384 |
+
{ url = "https://files.pythonhosted.org/packages/2b/fb/c492d6daa5ec067c2988ac80c61359ace5c4c674c532985ac5a123436cec/websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04", size = 174155, upload-time = "2025-03-05T20:03:25.321Z" },
|
| 2385 |
+
{ url = "https://files.pythonhosted.org/packages/68/a1/dcb68430b1d00b698ae7a7e0194433bce4f07ded185f0ee5fb21e2a2e91e/websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122", size = 176884, upload-time = "2025-03-05T20:03:27.934Z" },
|
| 2386 |
+
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" },
|
| 2387 |
+
]
|