Compare commits

...
Author SHA1 Message Date
praisonai-triage-agent[bot] c5f4feae62 Fix critical AgentOS bugs identified by reviewers
- Fix CORS configuration to prevent server startup crash
- Add role, tools, connections fields to DB schema and API models
- Fix activity list ordering (now returns newest instead of oldest)
- Add proper HTTP error handling across frontend
- Add defensive guards for undefined tools/connections arrays
- Update frontend status options to match backend validation
- Add request cancellation to prevent stale response handling

Addresses P0/P1 issues found by CodeRabbit, Greptile, Gemini reviewers.
2026-04-19 12:35:07 +00:00
Claude 8fa9a69f84 Move chat history to SQLite, modernize datetime + lifespan
- Add chat_history table with FK to agents(id) ON DELETE CASCADE and
  (agent_id, timestamp) index; migrate existing history_store.json on
  first startup when the table is empty.
- Replace the deprecated @app.on_event("startup") hook with a FastAPI
  lifespan context manager.
- Swap datetime.utcnow() for timezone-aware datetime.now(timezone.utc).
- Drop the non-thread-safe load_history/save_history JSON helpers; chat
  write/read/clear/activity endpoints now go through db.py, and agent
  deletion lets the FK cascade clean up history rows.
2026-04-19 12:25:39 +00:00
Claude 8293d5b106 Migrate agents persistence from JSON file to SQLite
Introduces backend/db.py with an `agents` table matching the new schema
(id, name, instructions, model, status, generation, parent_id,
performance_score, token_spend, exit_summary, created_at, updated_at)
and a one-time migration from agents_store.json on first startup when
the table is empty. Status is constrained to active/auditing/
decommissioned and parent_id is a self-referencing FK.

FastAPI endpoints are preserved. `llm` is kept as a two-way alias for
`model` for backwards compatibility, and legacy body fields (role,
tools, connections) are accepted but ignored so existing clients do
not break.
2026-04-19 12:05:39 +00:00
alejandro256 a2d6e6b889 Saved progress at the end of the loop
Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 0900f50b-e663-41d6-8eb3-0fb68d06e78d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0d762fb5-ec75-4083-b52b-16f8976607ce
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6e90e19c-4e5e-4ff7-9a97-710a94bbd6a8/0900f50b-e663-41d6-8eb3-0fb68d06e78d/8nsyWBB
Replit-Helium-Checkpoint-Created: true
2026-04-19 11:51:06 +00:00
alejandro256 17b7a6aa90 Add a modern web dashboard for managing AI agents
Implement a full-stack AgentOS dashboard with React/Vite frontend and FastAPI backend, enabling agent CRUD operations, chat, and activity logging.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 0900f50b-e663-41d6-8eb3-0fb68d06e78d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: d28e2992-56b7-45f3-9e96-b8ca2a548f7d
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6e90e19c-4e5e-4ff7-9a97-710a94bbd6a8/0900f50b-e663-41d6-8eb3-0fb68d06e78d/8nsyWBB
Replit-Helium-Checkpoint-Created: true
2026-04-19 11:32:00 +00:00
alejandro256 1513d729ba Set up the PraisonAI platform with essential dependencies and a startup script
Initialize the PraisonAI platform by adding a main entry point, defining project dependencies in pyproject.toml, creating a README for project documentation, and configuring the run script for the FastAPI server.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 0900f50b-e663-41d6-8eb3-0fb68d06e78d
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: ca2d51cc-b5aa-4b1d-9829-37bc47bbd705
Replit-Helium-Checkpoint-Created: true
2026-04-19 11:23:01 +00:00
23 changed files with 4078 additions and 0 deletions

No files matched your search

+60
View File
@@ -0,0 +1,60 @@
modules = ["python-3.12", "nodejs-20", "rust-stable", "bash"]
[agent]
expertMode = true
integrations = ["github:1.0.0"]
[nix]
channel = "stable-25_05"
packages = ["libxcrypt"]
[[ports]]
localPort = 5000
externalPort = 80
[[ports]]
localPort = 8000
externalPort = 8000
[workflows]
runButton = "Project"
[[workflows.workflow]]
name = "Project"
mode = "parallel"
author = "agent"
[[workflows.workflow.tasks]]
task = "workflow.run"
args = "Start application"
[[workflows.workflow.tasks]]
task = "workflow.run"
args = "Backend API"
[[workflows.workflow]]
name = "Start application"
author = "agent"
[workflows.workflow.metadata]
outputType = "webview"
[[workflows.workflow.tasks]]
task = "shell.exec"
args = "python run.py"
waitForPort = 5000
[[workflows.workflow]]
name = "Backend API"
author = "agent"
[[workflows.workflow.tasks]]
task = "shell.exec"
args = "python backend/main.py"
waitForPort = 8000
[workflows.workflow.metadata]
outputType = "console"
[deployment]
deploymentTarget = "autoscale"
run = ["python", "run.py"]
@@ -0,0 +1,168 @@
Build a clean, modern web dashboard for managing and interacting with AI agents powered by PraisonAI. Use React + Vite for the frontend, and a simple Python FastAPI backend. Keep it simple but functional — no overengineering.
---
## TECH STACK
- Frontend: React + Vite + TailwindCSS (CDN, no build step for Tailwind)
- Backend: FastAPI (Python)
- Storage: JSON files (no database needed yet, keep it simple)
- API calls: fetch() to the FastAPI backend
---
## PROJECT STRUCTURE
/backend
main.py # FastAPI app
agents_store.json # persists agent definitions
history_store.json # persists conversation history
/frontend
index.html
src/
App.jsx
components/
Sidebar.jsx
AgentList.jsx
AgentDetail.jsx
ChatPanel.jsx
ActivityLog.jsx
AgentForm.jsx
---
## BACKEND (FastAPI - main.py)
Create these REST endpoints:
### Agents CRUD
GET /agents → list all agents
POST /agents → create agent
PUT /agents/{id} → update agent
DELETE /agents/{id} → delete agent
GET /agents/{id} → get single agent details
### Chat
POST /agents/{id}/chat → send message to agent, returns response + activity log
GET /agents/{id}/history → get full conversation history
DELETE /agents/{id}/history → clear history
### Activity
GET /agents/{id}/activity → get activity log (tools used, connections, actions)
Each agent object should look like:
{
"id": "uuid",
"name": "string",
"role": "string",
"instructions": "string",
"tools": ["list of tool names"],
"connections": ["other agent ids or external APIs"],
"llm": "string (e.g. gpt-4o-mini)",
"created_at": "timestamp",
"status": "active | inactive"
}
Each chat call should:
1. Load agent config
2. Run a PraisonAI agent with that config
3. Capture stdout/tool calls/actions into an activity log entry
4. Save message + response + activity to history
5. Return all of it in one response
---
## FRONTEND LAYOUT
Split into 3 columns:
### LEFT SIDEBAR (narrow)
- App logo/name "AgentOS"
- List of all agents (name + status dot green/grey)
- "+ New Agent" button at bottom
- Click agent → loads it in center panel
### CENTER PANEL (main)
Two tabs at top: "Chat" | "Details"
#### Chat tab:
- Conversation history (bubbles: user right, agent left)
- Each agent message has a collapsible "Activity" section below it showing:
- What tools were called
- What external connections were used
- Raw output / reasoning steps
- Input box at bottom + Send button
- "Clear History" button top right
#### Details tab:
- Shows agent config (name, role, instructions, LLM, tools, connections)
- Edit button → opens inline form to modify
- Shows stats: total messages, last active, tools used count
### RIGHT PANEL (narrower)
- "Activity Feed" — real-time log of what the selected agent has done
- Each entry: timestamp, action type (tool_call, api_call, message, error), short description
- Color coded: blue=tool, green=success, red=error, grey=message
- Scrollable, newest at top
---
## AGENT FORM (Add/Edit modal or inline)
Fields:
- Name (text input)
- Role (text input, short description)
- Instructions / System Prompt (large textarea)
- LLM Model (dropdown: gpt-4o-mini, gpt-4o, claude-3-haiku, claude-sonnet, gemini-flash)
- Tools (multi-select checkboxes): web_search, code_interpreter, file_reader, calculator, wikipedia
- Connections (text inputs, add/remove rows): label + URL or agent name
- Status toggle (active/inactive)
Save / Cancel buttons
---
## STYLE GUIDE
- Dark theme (background #0f1117, panels #1a1d27, accent #6c63ff purple)
- Clean sans-serif font (Inter from Google Fonts)
- Subtle borders (#2a2d3e)
- Agent status dot: green (#22c55e) active, grey (#6b7280) inactive
- Activity log colors: blue (#3b82f6) tool, green (#22c55e) success, red (#ef4444) error
- Rounded corners (8px), subtle shadows
- Mobile responsive (sidebar collapses to hamburger on small screens)
---
## SAMPLE DATA
Pre-populate agents_store.json with 2 example agents so the UI is not empty:
Agent 1:
- Name: "Research Agent"
- Role: "Searches the web and summarizes information"
- Tools: web_search, wikipedia
- LLM: gpt-4o-mini
Agent 2:
- Name: "Code Agent"
- Role: "Writes and explains code"
- Tools: code_interpreter, calculator
- LLM: gpt-4o-mini
---
## IMPORTANT CONSTRAINTS
- No TypeScript (plain JSX only)
- No Redux (useState/useEffect only)
- No external UI libraries (just Tailwind utility classes)
- JSON file storage only (no SQLite, no Postgres)
- Keep each file under 200 lines if possible, split into components
- PraisonAI backend calls should use praisonaiagents library
- Add CORS middleware to FastAPI so frontend can call backend freely
- Read OPENAI_API_KEY from environment variable (os.environ)
---
## DELIVERABLE
- Working React frontend that talks to FastAPI backend
- I can add/edit/delete agents via the UI
- I can chat with any agent and see what it does
- History is saved and persists across page refreshes
- Activity log updates after each interaction
- README with how to run both frontend and backend
+24
View File
@@ -0,0 +1,24 @@
[
{
"id": "agent-001",
"name": "Research Agent",
"role": "Searches the web and summarizes information",
"instructions": "You are a research assistant. Search the web and Wikipedia to find accurate, up-to-date information. Summarize findings clearly and concisely. Always cite your sources.",
"tools": ["web_search", "wikipedia"],
"connections": [],
"llm": "gpt-4o-mini",
"created_at": "2025-01-01T00:00:00Z",
"status": "active"
},
{
"id": "agent-002",
"name": "Code Agent",
"role": "Writes and explains code",
"instructions": "You are a coding assistant. Write clean, well-commented code. Explain how code works step by step. Help debug issues and suggest improvements.",
"tools": ["code_interpreter", "calculator"],
"connections": [],
"llm": "gpt-4o-mini",
"created_at": "2025-01-01T00:00:00Z",
"status": "active"
}
]
+301
View File
@@ -0,0 +1,301 @@
"""SQLite persistence for AgentOS agents and chat history."""
import json
import os
import sqlite3
from contextlib import contextmanager
from datetime import datetime, timezone
from typing import Dict, Iterator, List, Optional
BASE_DIR = os.path.dirname(__file__)
DB_FILE = os.environ.get("AGENTOS_DB", os.path.join(BASE_DIR, "agentos.db"))
LEGACY_AGENTS_FILE = os.path.join(BASE_DIR, "agents_store.json")
LEGACY_HISTORY_FILE = os.path.join(BASE_DIR, "history_store.json")
ALLOWED_STATUS = {"active", "auditing", "decommissioned"}
SCHEMA = """
CREATE TABLE IF NOT EXISTS agents (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
instructions TEXT NOT NULL DEFAULT '',
model TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'auditing', 'decommissioned')),
generation INTEGER NOT NULL DEFAULT 1,
parent_id TEXT REFERENCES agents(id) ON DELETE SET NULL,
performance_score REAL,
token_spend INTEGER NOT NULL DEFAULT 0,
exit_summary TEXT,
role TEXT,
tools TEXT DEFAULT '[]',
connections TEXT DEFAULT '[]',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS chat_history (
id TEXT PRIMARY KEY,
agent_id TEXT NOT NULL REFERENCES agents(id) ON DELETE CASCADE,
user_message TEXT NOT NULL,
agent_response TEXT NOT NULL,
activity TEXT NOT NULL DEFAULT '[]',
timestamp TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_chat_history_agent_ts
ON chat_history(agent_id, timestamp);
"""
@contextmanager
def get_conn() -> Iterator[sqlite3.Connection]:
conn = sqlite3.connect(DB_FILE)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys = ON")
try:
yield conn
conn.commit()
finally:
conn.close()
def _now() -> str:
# Timezone-aware UTC timestamp, ISO-8601 with trailing Z to match prior shape.
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z"
def _row_to_dict(row: sqlite3.Row) -> Dict:
d = dict(row)
# Legacy alias so existing frontend/clients that read `llm` still work.
d["llm"] = d["model"]
# Parse JSON fields
try:
d["tools"] = json.loads(d.get("tools") or "[]")
except (json.JSONDecodeError, TypeError):
d["tools"] = []
try:
d["connections"] = json.loads(d.get("connections") or "[]")
except (json.JSONDecodeError, TypeError):
d["connections"] = []
return d
def init_db() -> None:
with get_conn() as conn:
conn.executescript(SCHEMA)
cur = conn.execute("SELECT COUNT(*) FROM agents")
if cur.fetchone()[0] == 0:
_migrate_from_json(conn)
cur = conn.execute("SELECT COUNT(*) FROM chat_history")
if cur.fetchone()[0] == 0:
_migrate_history_from_json(conn)
def _migrate_from_json(conn: sqlite3.Connection) -> None:
if not os.path.exists(LEGACY_AGENTS_FILE):
return
try:
with open(LEGACY_AGENTS_FILE) as f:
legacy = json.load(f)
except (OSError, json.JSONDecodeError):
return
for a in legacy:
status = a.get("status", "active")
if status not in ALLOWED_STATUS:
status = "active"
created = a.get("created_at") or _now()
conn.execute(
"""
INSERT OR IGNORE INTO agents
(id, name, instructions, model, status, generation, parent_id,
performance_score, token_spend, exit_summary, role, tools, connections, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, 1, NULL, NULL, 0, NULL, ?, ?, ?, ?, ?)
""",
(
a["id"],
a.get("name", "Unnamed"),
a.get("instructions", ""),
a.get("llm") or a.get("model") or "gpt-4o-mini",
status,
a.get("role"),
json.dumps(a.get("tools", [])),
json.dumps(a.get("connections", [])),
created,
created,
),
)
def _migrate_history_from_json(conn: sqlite3.Connection) -> None:
if not os.path.exists(LEGACY_HISTORY_FILE):
return
try:
with open(LEGACY_HISTORY_FILE) as f:
legacy = json.load(f)
except (OSError, json.JSONDecodeError):
return
if not isinstance(legacy, dict):
return
for agent_id, entries in legacy.items():
if not isinstance(entries, list):
continue
for entry in entries:
conn.execute(
"""
INSERT OR IGNORE INTO chat_history
(id, agent_id, user_message, agent_response, activity, timestamp)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
entry.get("id") or os.urandom(4).hex(),
agent_id,
entry.get("user_message", ""),
entry.get("agent_response", ""),
json.dumps(entry.get("activity", [])),
entry.get("timestamp") or _now(),
),
)
def list_agents() -> List[Dict]:
with get_conn() as conn:
rows = conn.execute(
"SELECT * FROM agents ORDER BY created_at ASC"
).fetchall()
return [_row_to_dict(r) for r in rows]
def get_agent(agent_id: str) -> Optional[Dict]:
with get_conn() as conn:
row = conn.execute(
"SELECT * FROM agents WHERE id = ?", (agent_id,)
).fetchone()
return _row_to_dict(row) if row else None
def create_agent(agent: Dict) -> Dict:
now = _now()
with get_conn() as conn:
conn.execute(
"""
INSERT INTO agents
(id, name, instructions, model, status, generation, parent_id,
performance_score, token_spend, exit_summary, role, tools, connections, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""",
(
agent["id"],
agent["name"],
agent.get("instructions", ""),
agent["model"],
agent.get("status", "active"),
agent.get("generation", 1),
agent.get("parent_id"),
agent.get("performance_score"),
agent.get("token_spend", 0),
agent.get("exit_summary"),
agent.get("role"),
json.dumps(agent.get("tools", [])),
json.dumps(agent.get("connections", [])),
now,
now,
),
)
return get_agent(agent["id"]) # type: ignore[return-value]
UPDATABLE_FIELDS = {
"name",
"instructions",
"model",
"status",
"generation",
"parent_id",
"performance_score",
"token_spend",
"exit_summary",
"role",
"tools",
"connections",
}
def update_agent(agent_id: str, fields: Dict) -> Optional[Dict]:
sets = {k: v for k, v in fields.items() if k in UPDATABLE_FIELDS}
if not sets:
return get_agent(agent_id)
# Serialize JSON fields
for k in ["tools", "connections"]:
if k in sets and isinstance(sets[k], list):
sets[k] = json.dumps(sets[k])
columns = ", ".join(f"{k} = ?" for k in sets) + ", updated_at = ?"
values = list(sets.values()) + [_now(), agent_id]
with get_conn() as conn:
cur = conn.execute(
f"UPDATE agents SET {columns} WHERE id = ?", values
)
if cur.rowcount == 0:
return None
return get_agent(agent_id)
def delete_agent(agent_id: str) -> bool:
with get_conn() as conn:
cur = conn.execute("DELETE FROM agents WHERE id = ?", (agent_id,))
return cur.rowcount > 0
# ----- chat history --------------------------------------------------------
def _row_to_history(row: sqlite3.Row) -> Dict:
d = dict(row)
try:
d["activity"] = json.loads(d.get("activity") or "[]")
except json.JSONDecodeError:
d["activity"] = []
d.pop("agent_id", None)
return d
def add_history_entry(agent_id: str, entry: Dict) -> Dict:
with get_conn() as conn:
conn.execute(
"""
INSERT INTO chat_history
(id, agent_id, user_message, agent_response, activity, timestamp)
VALUES (?, ?, ?, ?, ?, ?)
""",
(
entry["id"],
agent_id,
entry["user_message"],
entry["agent_response"],
json.dumps(entry.get("activity", [])),
entry["timestamp"],
),
)
return entry
def list_history(agent_id: str) -> List[Dict]:
with get_conn() as conn:
rows = conn.execute(
"SELECT * FROM chat_history WHERE agent_id = ? ORDER BY timestamp ASC",
(agent_id,),
).fetchall()
return [_row_to_history(r) for r in rows]
def clear_history(agent_id: str) -> None:
with get_conn() as conn:
conn.execute("DELETE FROM chat_history WHERE agent_id = ?", (agent_id,))
def list_activity(agent_id: str, limit: int = 100) -> List[Dict]:
"""Flattened activity across all of an agent's chat entries, newest first, capped."""
entries = list_history(agent_id)
activity: List[Dict] = []
for entry in reversed(entries):
activity.extend(entry.get("activity", []))
return activity[:limit]
+1
View File
@@ -0,0 +1 @@
{}
+234
View File
@@ -0,0 +1,234 @@
"""AgentOS FastAPI backend."""
import os
import uuid
from contextlib import asynccontextmanager
from datetime import datetime, timezone
from typing import Dict, List, Optional
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, ConfigDict
try:
from . import db as agentdb # package import (uvicorn backend.main:app)
except ImportError: # pragma: no cover - script execution fallback
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import db as agentdb # type: ignore
@asynccontextmanager
async def lifespan(app: FastAPI):
agentdb.init_db()
yield
app = FastAPI(title="AgentOS API", lifespan=lifespan)
allowed_origins = os.environ.get("AGENTOS_CORS_ORIGINS", "http://localhost:5173").split(",")
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def _now_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f") + "Z"
class AgentCreate(BaseModel):
# Accept fields for frontend compatibility.
# `protected_namespaces=()` silences Pydantic v2 warnings about the
# `model` field name colliding with its protected namespace.
model_config = ConfigDict(extra="ignore", protected_namespaces=())
name: str
instructions: str = ""
model: Optional[str] = None
llm: Optional[str] = None # legacy alias for `model`
status: str = "active"
generation: int = 1
parent_id: Optional[str] = None
performance_score: Optional[float] = None
token_spend: int = 0
exit_summary: Optional[str] = None
role: Optional[str] = None
tools: List[str] = []
connections: List[str] = []
class AgentUpdate(BaseModel):
model_config = ConfigDict(extra="ignore", protected_namespaces=())
name: Optional[str] = None
instructions: Optional[str] = None
model: Optional[str] = None
llm: Optional[str] = None # legacy alias for `model`
status: Optional[str] = None
generation: Optional[int] = None
parent_id: Optional[str] = None
performance_score: Optional[float] = None
token_spend: Optional[int] = None
exit_summary: Optional[str] = None
role: Optional[str] = None
tools: Optional[List[str]] = None
connections: Optional[List[str]] = None
class ChatMessage(BaseModel):
message: str
def _validate_status(status: str) -> None:
if status not in agentdb.ALLOWED_STATUS:
raise HTTPException(
status_code=400,
detail=f"status must be one of {sorted(agentdb.ALLOWED_STATUS)}",
)
@app.get("/agents")
def list_agents():
return agentdb.list_agents()
@app.get("/agents/{agent_id}")
def get_agent(agent_id: str):
agent = agentdb.get_agent(agent_id)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
return agent
@app.post("/agents", status_code=201)
def create_agent(data: AgentCreate):
_validate_status(data.status)
if data.parent_id and not agentdb.get_agent(data.parent_id):
raise HTTPException(status_code=400, detail="parent_id does not exist")
record = {
"id": f"agent-{uuid.uuid4().hex[:8]}",
"name": data.name,
"instructions": data.instructions,
"model": data.model or data.llm or "gpt-4o-mini",
"status": data.status,
"generation": data.generation,
"parent_id": data.parent_id,
"performance_score": data.performance_score,
"token_spend": data.token_spend,
"exit_summary": data.exit_summary,
"role": data.role,
"tools": data.tools,
"connections": data.connections,
}
return agentdb.create_agent(record)
@app.put("/agents/{agent_id}")
def update_agent(agent_id: str, data: AgentUpdate):
fields = data.model_dump(exclude_none=True)
# Legacy `llm` alias -> `model`
if "llm" in fields and "model" not in fields:
fields["model"] = fields["llm"]
fields.pop("llm", None)
if "status" in fields:
_validate_status(fields["status"])
if fields.get("parent_id") and not agentdb.get_agent(fields["parent_id"]):
raise HTTPException(status_code=400, detail="parent_id does not exist")
updated = agentdb.update_agent(agent_id, fields)
if updated is None:
raise HTTPException(status_code=404, detail="Agent not found")
return updated
@app.delete("/agents/{agent_id}", status_code=204)
def delete_agent(agent_id: str):
# chat_history rows cascade via FK ON DELETE CASCADE
if not agentdb.delete_agent(agent_id):
raise HTTPException(status_code=404, detail="Agent not found")
@app.post("/agents/{agent_id}/chat")
def chat_with_agent(agent_id: str, body: ChatMessage):
agent = agentdb.get_agent(agent_id)
if not agent:
raise HTTPException(status_code=404, detail="Agent not found")
activity: List[Dict] = []
response_text = ""
api_key = os.environ.get("OPENAI_API_KEY", "")
if api_key:
try:
from praisonaiagents import Agent
activity.append(_log("tool", f"Initializing {agent['model']} agent"))
pa_agent = Agent(
name=agent["name"],
instructions=agent["instructions"],
llm=agent["model"],
)
activity.append(_log("tool", "Running agent inference"))
result = pa_agent.start(body.message)
response_text = str(result)
activity.append(_log("success", "Agent response generated"))
except Exception as e:
activity.append(_log("error", f"Agent error: {str(e)[:120]}"))
response_text = f"Agent encountered an error: {str(e)[:200]}"
else:
activity.append(_log("message", "No OPENAI_API_KEY set — using demo mode"))
response_text = _demo_response(agent, body.message)
activity.append(_log("success", "Demo response generated"))
entry = {
"id": uuid.uuid4().hex[:8],
"user_message": body.message,
"agent_response": response_text,
"activity": activity,
"timestamp": _now_iso(),
}
agentdb.add_history_entry(agent_id, entry)
return entry
@app.get("/agents/{agent_id}/history")
def get_history(agent_id: str):
return agentdb.list_history(agent_id)
@app.delete("/agents/{agent_id}/history", status_code=204)
def clear_history(agent_id: str):
agentdb.clear_history(agent_id)
@app.get("/agents/{agent_id}/activity")
def get_activity(agent_id: str):
return agentdb.list_activity(agent_id, limit=100)
def _log(action_type: str, description: str) -> Dict:
return {
"id": uuid.uuid4().hex[:8],
"timestamp": _now_iso(),
"type": action_type,
"description": description,
}
def _demo_response(agent: Dict, message: str) -> str:
name = agent.get("name", "Agent")
return (
f"Hi! I'm {name}. "
f"You asked: \"{message}\"\n\n"
f"I'm running on {agent.get('model', 'unknown model')}. "
f"To enable real AI responses, set your OPENAI_API_KEY environment variable."
)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="localhost", port=8000)
+47
View File
@@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AgentOS</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><rect width='100' height='100' rx='20' fill='%236c63ff'/><text y='.9em' font-size='70' x='15' fill='white'>A</text></svg>" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
bg: '#0f1117',
panel: '#1a1d27',
accent: '#6c63ff',
border: '#2a2d3e',
'status-active': '#22c55e',
'status-inactive': '#6b7280',
'activity-tool': '#3b82f6',
'activity-success': '#22c55e',
'activity-error': '#ef4444',
'activity-message': '#6b7280',
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
},
},
}
</script>
<style>
body { background-color: #0f1117; color: #e2e8f0; font-family: 'Inter', sans-serif; }
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-track { background: #1a1d27; }
::-webkit-scrollbar-thumb { background: #2a2d3e; border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: #6c63ff; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+1677
View File
File diff suppressed because it is too large. Load diff
+18
View File
@@ -0,0 +1,18 @@
{
"name": "agentos-frontend",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --host 0.0.0.0 --port 5000",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.2.0",
"vite": "^5.0.0"
}
}
+260
View File
@@ -0,0 +1,260 @@
import { useState, useEffect, useCallback } from 'react'
import Sidebar from './components/Sidebar'
import ChatPanel from './components/ChatPanel'
import AgentDetail from './components/AgentDetail'
import ActivityLog from './components/ActivityLog'
import AgentForm from './components/AgentForm'
const API = '/api'
export default function App() {
const [agents, setAgents] = useState([])
const [selectedId, setSelectedId] = useState(null)
const [activeTab, setActiveTab] = useState('chat')
const [showForm, setShowForm] = useState(false)
const [editingAgent, setEditingAgent] = useState(null)
const [sidebarOpen, setSidebarOpen] = useState(true)
const [history, setHistory] = useState([])
const [activity, setActivity] = useState([])
const [loading, setLoading] = useState(false)
const fetchAgents = useCallback(async () => {
try {
const res = await fetch(`${API}/agents`)
const data = await res.json()
setAgents(data)
if (!selectedId && data.length > 0) setSelectedId(data[0].id)
} catch (e) {
console.error('Failed to fetch agents', e)
}
}, [selectedId])
const fetchHistory = useCallback(async (id, signal) => {
if (!id) return
try {
const res = await fetch(`${API}/agents/${id}/history`, { signal })
if (!res.ok) return
const data = await res.json()
setHistory(data)
} catch (e) {
if (e.name !== 'AbortError') console.error(e)
}
}, [])
const fetchActivity = useCallback(async (id, signal) => {
if (!id) return
try {
const res = await fetch(`${API}/agents/${id}/activity`, { signal })
if (!res.ok) return
const data = await res.json()
setActivity(data)
} catch (e) {
if (e.name !== 'AbortError') console.error(e)
}
}, [])
useEffect(() => { fetchAgents() }, [])
useEffect(() => {
if (selectedId) {
const controller = new AbortController()
fetchHistory(selectedId, controller.signal)
fetchActivity(selectedId, controller.signal)
return () => controller.abort()
}
}, [selectedId, fetchHistory, fetchActivity])
const handleSelectAgent = (id) => {
setSelectedId(id)
setActiveTab('chat')
}
const handleSendMessage = async (message) => {
if (!selectedId) return
setLoading(true)
try {
const res = await fetch(`${API}/agents/${selectedId}/chat`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message }),
})
if (!res.ok) {
throw new Error(`Chat failed: ${res.status}`)
}
const entry = await res.json()
setHistory(prev => [...prev, entry])
setActivity(prev => [...(entry.activity || []), ...prev])
} catch (e) {
console.error(e)
} finally {
setLoading(false)
}
}
const handleClearHistory = async () => {
if (!selectedId) return
try {
const res = await fetch(`${API}/agents/${selectedId}/history`, { method: 'DELETE' })
if (!res.ok) throw new Error(`Clear history failed: ${res.status}`)
setHistory([])
setActivity([])
} catch (e) {
console.error(e)
}
}
const handleSaveAgent = async (data) => {
try {
if (editingAgent) {
const res = await fetch(`${API}/agents/${editingAgent.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!res.ok) throw new Error(`Update agent failed: ${res.status}`)
const updated = await res.json()
setAgents(prev => prev.map(a => a.id === updated.id ? updated : a))
} else {
const res = await fetch(`${API}/agents`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (!res.ok) throw new Error(`Create agent failed: ${res.status}`)
const created = await res.json()
setAgents(prev => [...prev, created])
setSelectedId(created.id)
}
setShowForm(false)
setEditingAgent(null)
} catch (e) {
console.error(e)
alert('Could not save agent. Please try again.')
}
}
const handleDeleteAgent = async (id) => {
if (!window.confirm('Delete this agent?')) return
try {
const res = await fetch(`${API}/agents/${id}`, { method: 'DELETE' })
if (!res.ok) throw new Error(`Delete agent failed: ${res.status}`)
setAgents(prev => prev.filter(a => a.id !== id))
if (selectedId === id) {
const remaining = agents.filter(a => a.id !== id)
setSelectedId(remaining.length > 0 ? remaining[0].id : null)
}
} catch (e) {
console.error(e)
alert('Could not delete agent. Please try again.')
}
}
const selectedAgent = agents.find(a => a.id === selectedId)
return (
<div className="flex h-screen overflow-hidden" style={{ background: '#0f1117' }}>
{/* Mobile overlay */}
{sidebarOpen && (
<div className="fixed inset-0 bg-black/50 z-10 md:hidden" onClick={() => setSidebarOpen(false)} />
)}
{/* Sidebar */}
<div className={`
fixed md:relative z-20 h-full transition-transform duration-300
${sidebarOpen ? 'translate-x-0' : '-translate-x-full md:translate-x-0'}
${sidebarOpen ? 'w-64' : 'w-0 md:w-64'}
`}>
<Sidebar
agents={agents}
selectedId={selectedId}
onSelect={handleSelectAgent}
onNewAgent={() => { setEditingAgent(null); setShowForm(true) }}
onClose={() => setSidebarOpen(false)}
/>
</div>
{/* Main content */}
<div className="flex flex-1 min-w-0 overflow-hidden">
{/* Center panel */}
<div className="flex flex-col flex-1 min-w-0 overflow-hidden border-x" style={{ borderColor: '#2a2d3e' }}>
{/* Header */}
<div className="flex items-center px-4 py-3 border-b shrink-0" style={{ background: '#1a1d27', borderColor: '#2a2d3e' }}>
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="mr-3 p-1.5 rounded-lg hover:bg-white/10 transition-colors md:hidden"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
{selectedAgent ? (
<>
<div className="flex items-center gap-2 flex-1">
<div className={`w-2 h-2 rounded-full shrink-0 ${selectedAgent.status === 'active' ? 'bg-status-active' : 'bg-status-inactive'}`} style={{ background: selectedAgent.status === 'active' ? '#22c55e' : '#6b7280' }} />
<span className="font-semibold truncate">{selectedAgent.name}</span>
<span className="text-sm text-gray-400 truncate hidden sm:block">— {selectedAgent.role}</span>
</div>
<div className="flex gap-1 ml-2">
<button
onClick={() => setActiveTab('chat')}
className={`px-3 py-1.5 text-sm rounded-lg font-medium transition-colors ${activeTab === 'chat' ? 'text-white' : 'text-gray-400 hover:text-white'}`}
style={activeTab === 'chat' ? { background: '#6c63ff' } : {}}
>Chat</button>
<button
onClick={() => setActiveTab('details')}
className={`px-3 py-1.5 text-sm rounded-lg font-medium transition-colors ${activeTab === 'details' ? 'text-white' : 'text-gray-400 hover:text-white'}`}
style={activeTab === 'details' ? { background: '#6c63ff' } : {}}
>Details</button>
</div>
</>
) : (
<span className="text-gray-400 text-sm">No agent selected</span>
)}
</div>
{/* Panel content */}
<div className="flex-1 overflow-hidden">
{!selectedAgent ? (
<div className="flex flex-col items-center justify-center h-full text-gray-500">
<svg className="w-16 h-16 mb-4 opacity-30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17H3a2 2 0 01-2-2V5a2 2 0 012-2h14a2 2 0 012 2v10a2 2 0 01-2 2h-2" />
</svg>
<p className="text-lg font-medium">Select or create an agent</p>
<p className="text-sm mt-1">Choose an agent from the sidebar to get started</p>
</div>
) : activeTab === 'chat' ? (
<ChatPanel
agent={selectedAgent}
history={history}
loading={loading}
onSend={handleSendMessage}
onClearHistory={handleClearHistory}
/>
) : (
<AgentDetail
agent={selectedAgent}
history={history}
onEdit={() => { setEditingAgent(selectedAgent); setShowForm(true) }}
onDelete={() => handleDeleteAgent(selectedAgent.id)}
/>
)}
</div>
</div>
{/* Right activity panel */}
<div className="hidden lg:flex flex-col w-72 shrink-0 overflow-hidden" style={{ background: '#1a1d27' }}>
<ActivityLog activity={activity} />
</div>
</div>
{/* Agent form modal */}
{showForm && (
<AgentForm
agent={editingAgent}
onSave={handleSaveAgent}
onCancel={() => { setShowForm(false); setEditingAgent(null) }}
/>
)}
</div>
)
}
+61
View File
@@ -0,0 +1,61 @@
const TYPE_CONFIG = {
tool: { color: '#3b82f6', bg: 'rgba(59,130,246,0.1)', label: 'TOOL', icon: '⚙️' },
success: { color: '#22c55e', bg: 'rgba(34,197,94,0.1)', label: 'OK', icon: '✓' },
error: { color: '#ef4444', bg: 'rgba(239,68,68,0.1)', label: 'ERR', icon: '✗' },
message: { color: '#6b7280', bg: 'rgba(107,114,128,0.1)', label: 'MSG', icon: '●' },
}
function ActivityItem({ item }) {
const cfg = TYPE_CONFIG[item.type] || TYPE_CONFIG.message
return (
<div className="flex items-start gap-2.5 py-2.5 border-b" style={{ borderColor: '#2a2d3e' }}>
<div className="w-5 h-5 rounded flex items-center justify-center shrink-0 mt-0.5 text-xs font-bold" style={{ background: cfg.bg, color: cfg.color }}>
{cfg.icon}
</div>
<div className="flex-1 min-w-0">
<p className="text-xs leading-snug" style={{ color: '#d1d5db' }}>
{item.description}
</p>
<p className="text-xs mt-0.5" style={{ color: '#4b5563' }}>
{new Date(item.timestamp).toLocaleTimeString()}
</p>
</div>
</div>
)
}
export default function ActivityLog({ activity }) {
return (
<div className="flex flex-col h-full border-l" style={{ borderColor: '#2a2d3e' }}>
<div className="px-4 py-3 border-b shrink-0" style={{ borderColor: '#2a2d3e' }}>
<div className="flex items-center justify-between">
<h3 className="text-sm font-semibold text-white">Activity Feed</h3>
<span className="text-xs px-2 py-0.5 rounded-full" style={{ background: 'rgba(108,99,255,0.15)', color: '#818cf8' }}>
{activity.length}
</span>
</div>
{/* Legend */}
<div className="flex flex-wrap gap-x-3 gap-y-1 mt-2">
{Object.entries(TYPE_CONFIG).map(([key, cfg]) => (
<div key={key} className="flex items-center gap-1 text-xs" style={{ color: cfg.color }}>
<span>{cfg.icon}</span>
<span style={{ color: '#6b7280' }}>{key}</span>
</div>
))}
</div>
</div>
<div className="flex-1 overflow-y-auto px-4">
{activity.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center py-8" style={{ color: '#4b5563' }}>
<div className="text-3xl mb-2 opacity-40">📋</div>
<p className="text-xs">No activity yet.<br />Send a message to see logs.</p>
</div>
) : (
activity.map(item => <ActivityItem key={item.id} item={item} />)
)}
</div>
</div>
)
}
+133
View File
@@ -0,0 +1,133 @@
export default function AgentDetail({ agent, history, onEdit, onDelete }) {
const totalMessages = history.length
const lastActive = history.length > 0
? new Date(history[history.length - 1].timestamp).toLocaleString()
: 'Never'
const toolsUsed = new Set()
history.forEach(entry => {
entry.activity?.forEach(a => {
if (a.type === 'tool') toolsUsed.add(a.description)
})
})
const TOOL_ICONS = {
web_search: '🔍',
wikipedia: '📖',
code_interpreter: '💻',
file_reader: '📄',
calculator: '🧮',
}
return (
<div className="h-full overflow-y-auto px-6 py-6">
{/* Header */}
<div className="flex items-start justify-between mb-6">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-2xl flex items-center justify-center text-xl font-bold" style={{ background: 'rgba(108,99,255,0.2)', border: '1px solid rgba(108,99,255,0.3)', color: '#6c63ff' }}>
{agent.name.charAt(0).toUpperCase()}
</div>
<div>
<h2 className="text-xl font-bold text-white">{agent.name}</h2>
<p className="text-sm mt-0.5" style={{ color: '#9ca3af' }}>{agent.role}</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className={`px-2.5 py-1 rounded-full text-xs font-medium`}
style={{ background: agent.status === 'active' ? 'rgba(34,197,94,0.15)' : 'rgba(107,114,128,0.15)', color: agent.status === 'active' ? '#22c55e' : '#6b7280' }}>
{agent.status}
</span>
<button
onClick={onEdit}
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-all hover:opacity-90"
style={{ background: '#6c63ff', color: 'white' }}
>
Edit
</button>
<button
onClick={onDelete}
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-colors"
style={{ border: '1px solid #ef4444', color: '#ef4444' }}
>
Delete
</button>
</div>
</div>
{/* Stats */}
<div className="grid grid-cols-3 gap-3 mb-6">
{[
{ label: 'Total Messages', value: totalMessages },
{ label: 'Tools Used', value: toolsUsed.size },
{ label: 'Last Active', value: lastActive, small: true },
].map(stat => (
<div key={stat.label} className="rounded-xl p-4" style={{ background: '#0f1117', border: '1px solid #2a2d3e' }}>
<p className="text-xs mb-1" style={{ color: '#6b7280' }}>{stat.label}</p>
<p className={`font-semibold text-white ${stat.small ? 'text-xs' : 'text-xl'}`}>{stat.value}</p>
</div>
))}
</div>
{/* Instructions */}
<Section title="System Instructions">
<p className="text-sm leading-relaxed whitespace-pre-wrap" style={{ color: '#d1d5db' }}>
{agent.instructions || <span style={{ color: '#6b7280' }}>No instructions set</span>}
</p>
</Section>
{/* Config */}
<div className="grid grid-cols-2 gap-4 mb-4">
<Section title="LLM Model">
<span className="inline-block px-3 py-1.5 rounded-lg text-sm font-medium" style={{ background: 'rgba(108,99,255,0.15)', color: '#818cf8', border: '1px solid rgba(108,99,255,0.2)' }}>
{agent.llm}
</span>
</Section>
<Section title="Status">
<span className="inline-flex items-center gap-2 text-sm" style={{ color: '#d1d5db' }}>
<span className="w-2 h-2 rounded-full" style={{ background: agent.status === 'active' ? '#22c55e' : '#6b7280' }} />
{agent.status}
</span>
</Section>
</div>
{/* Tools */}
<Section title="Tools">
{(agent.tools?.length ?? 0) === 0 ? (
<p className="text-sm" style={{ color: '#6b7280' }}>No tools configured</p>
) : (
<div className="flex flex-wrap gap-2">
{(agent.tools || []).map(tool => (
<span key={tool} className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm" style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#d1d5db' }}>
<span>{TOOL_ICONS[tool] || '🔧'}</span>
{tool}
</span>
))}
</div>
)}
</Section>
{/* Connections */}
{(agent.connections?.length ?? 0) > 0 && (
<Section title="Connections">
<div className="space-y-2">
{(agent.connections || []).map((conn, i) => (
<div key={i} className="flex items-center gap-2 text-sm px-3 py-2 rounded-lg" style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#d1d5db' }}>
<span style={{ color: '#6b7280' }}>→</span>
{conn}
</div>
))}
</div>
</Section>
)}
</div>
)
}
function Section({ title, children }) {
return (
<div className="mb-4 rounded-xl p-4" style={{ background: '#0f1117', border: '1px solid #2a2d3e' }}>
<h3 className="text-xs font-semibold uppercase tracking-wider mb-3" style={{ color: '#6b7280' }}>{title}</h3>
{children}
</div>
)
}
+247
View File
@@ -0,0 +1,247 @@
import { useState } from 'react'
const AVAILABLE_TOOLS = [
{ id: 'web_search', label: 'Web Search', icon: '🔍' },
{ id: 'code_interpreter', label: 'Code Interpreter', icon: '💻' },
{ id: 'file_reader', label: 'File Reader', icon: '📄' },
{ id: 'calculator', label: 'Calculator', icon: '🧮' },
{ id: 'wikipedia', label: 'Wikipedia', icon: '📖' },
]
const LLM_MODELS = [
'gpt-4o-mini',
'gpt-4o',
'claude-3-haiku',
'claude-3-5-sonnet',
'gemini-1.5-flash',
]
export default function AgentForm({ agent, onSave, onCancel }) {
const [form, setForm] = useState({
name: agent?.name || '',
role: agent?.role || '',
instructions: agent?.instructions || '',
llm: agent?.llm || 'gpt-4o-mini',
tools: agent?.tools || [],
connections: agent?.connections || [],
status: agent?.status || 'active',
})
const [newConn, setNewConn] = useState('')
const set = (key, val) => setForm(prev => ({ ...prev, [key]: val }))
const toggleTool = (toolId) => {
set('tools', form.tools.includes(toolId)
? form.tools.filter(t => t !== toolId)
: [...form.tools, toolId])
}
const addConnection = () => {
if (!newConn.trim()) return
set('connections', [...form.connections, newConn.trim()])
setNewConn('')
}
const removeConnection = (i) => {
set('connections', form.connections.filter((_, idx) => idx !== i))
}
const handleSubmit = (e) => {
e.preventDefault()
if (!form.name.trim()) return
onSave(form)
}
return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4" style={{ background: 'rgba(0,0,0,0.7)', backdropFilter: 'blur(4px)' }}>
<div className="w-full max-w-2xl max-h-[90vh] overflow-y-auto rounded-2xl" style={{ background: '#1a1d27', border: '1px solid #2a2d3e' }}>
<form onSubmit={handleSubmit}>
{/* Header */}
<div className="flex items-center justify-between px-6 py-4 border-b" style={{ borderColor: '#2a2d3e' }}>
<h2 className="text-lg font-bold text-white">{agent ? 'Edit Agent' : 'New Agent'}</h2>
<button type="button" onClick={onCancel} className="p-1.5 rounded-lg hover:bg-white/10 transition-colors text-gray-400">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div className="px-6 py-5 space-y-5">
{/* Name + Role */}
<div className="grid grid-cols-2 gap-4">
<Field label="Name *">
<input
type="text"
value={form.name}
onChange={e => set('name', e.target.value)}
placeholder="Research Agent"
required
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none"
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
onFocus={e => e.target.style.borderColor = '#6c63ff'}
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
/>
</Field>
<Field label="Role">
<input
type="text"
value={form.role}
onChange={e => set('role', e.target.value)}
placeholder="Searches the web..."
required
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none"
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
onFocus={e => e.target.style.borderColor = '#6c63ff'}
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
/>
</Field>
</div>
{/* Instructions */}
<Field label="Instructions / System Prompt">
<textarea
value={form.instructions}
onChange={e => set('instructions', e.target.value)}
placeholder="You are a helpful assistant that..."
rows={4}
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none resize-none"
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
onFocus={e => e.target.style.borderColor = '#6c63ff'}
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
/>
</Field>
{/* LLM + Status */}
<div className="grid grid-cols-2 gap-4">
<Field label="LLM Model">
<select
value={form.llm}
onChange={e => set('llm', e.target.value)}
className="w-full px-3 py-2.5 rounded-lg text-sm outline-none"
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
>
{LLM_MODELS.map(m => <option key={m} value={m}>{m}</option>)}
</select>
</Field>
<Field label="Status">
<div className="flex gap-3 pt-1">
{['active', 'auditing', 'decommissioned'].map(s => (
<label key={s} className="flex items-center gap-2 cursor-pointer">
<div
onClick={() => set('status', s)}
className="w-4 h-4 rounded-full border-2 flex items-center justify-center transition-colors cursor-pointer"
style={{ borderColor: form.status === s ? '#6c63ff' : '#4b5563', background: form.status === s ? '#6c63ff' : 'transparent' }}
>
{form.status === s && <div className="w-1.5 h-1.5 rounded-full bg-white" />}
</div>
<span className="text-sm" style={{ color: '#d1d5db' }}>{s}</span>
</label>
))}
</div>
</Field>
</div>
{/* Tools */}
<Field label="Tools">
<div className="grid grid-cols-2 sm:grid-cols-3 gap-2">
{AVAILABLE_TOOLS.map(tool => {
const active = form.tools.includes(tool.id)
return (
<button
key={tool.id}
type="button"
onClick={() => toggleTool(tool.id)}
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm transition-all"
style={{
background: active ? 'rgba(108,99,255,0.15)' : '#0f1117',
border: `1px solid ${active ? '#6c63ff' : '#2a2d3e'}`,
color: active ? '#818cf8' : '#9ca3af',
}}
>
<span>{tool.icon}</span>
<span>{tool.label}</span>
</button>
)
})}
</div>
</Field>
{/* Connections */}
<Field label="Connections">
<div className="space-y-2">
{form.connections.map((conn, i) => (
<div key={i} className="flex items-center gap-2">
<span className="flex-1 px-3 py-2 rounded-lg text-sm" style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#d1d5db' }}>
{conn}
</span>
<button
type="button"
onClick={() => removeConnection(i)}
className="p-1.5 rounded-lg hover:bg-red-500/20 transition-colors"
style={{ color: '#ef4444' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
))}
<div className="flex gap-2">
<input
type="text"
value={newConn}
onChange={e => setNewConn(e.target.value)}
onKeyDown={e => e.key === 'Enter' && (e.preventDefault(), addConnection())}
placeholder="Agent name or API URL..."
className="flex-1 px-3 py-2 rounded-lg text-sm outline-none"
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
onFocus={e => e.target.style.borderColor = '#6c63ff'}
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
/>
<button
type="button"
onClick={addConnection}
className="px-3 py-2 rounded-lg text-sm transition-colors"
style={{ background: '#2a2d3e', color: '#d1d5db' }}
>
Add
</button>
</div>
</div>
</Field>
</div>
{/* Footer */}
<div className="flex gap-3 justify-end px-6 py-4 border-t" style={{ borderColor: '#2a2d3e' }}>
<button
type="button"
onClick={onCancel}
className="px-5 py-2.5 rounded-xl text-sm font-medium transition-colors hover:bg-white/5"
style={{ border: '1px solid #2a2d3e', color: '#9ca3af' }}
>
Cancel
</button>
<button
type="submit"
className="px-5 py-2.5 rounded-xl text-sm font-medium transition-all hover:opacity-90 active:scale-95"
style={{ background: '#6c63ff', color: 'white' }}
>
{agent ? 'Save Changes' : 'Create Agent'}
</button>
</div>
</form>
</div>
</div>
)
}
function Field({ label, children }) {
return (
<div>
<label className="block text-xs font-semibold uppercase tracking-wider mb-2" style={{ color: '#6b7280' }}>
{label}
</label>
{children}
</div>
)
}
+153
View File
@@ -0,0 +1,153 @@
import { useState, useRef, useEffect } from 'react'
function ActivityEntry({ item }) {
const colorMap = {
tool: '#3b82f6',
success: '#22c55e',
error: '#ef4444',
message: '#6b7280',
}
const color = colorMap[item.type] || '#6b7280'
const labels = { tool: 'TOOL', success: 'OK', error: 'ERR', message: 'MSG' }
return (
<div className="flex items-start gap-2 text-xs py-0.5">
<span className="font-mono font-bold shrink-0" style={{ color }}>{labels[item.type] || 'LOG'}</span>
<span style={{ color: '#94a3b8' }}>{item.description}</span>
</div>
)
}
function MessageBubble({ entry }) {
const [expanded, setExpanded] = useState(false)
const hasActivity = entry.activity && entry.activity.length > 0
return (
<div className="space-y-2 mb-4">
{/* User message */}
<div className="flex justify-end">
<div className="max-w-[75%] px-4 py-2.5 rounded-2xl rounded-tr-sm text-sm" style={{ background: '#6c63ff', color: 'white' }}>
{entry.user_message}
</div>
</div>
{/* Agent message */}
<div className="flex justify-start flex-col">
<div className="max-w-[80%] px-4 py-2.5 rounded-2xl rounded-tl-sm text-sm" style={{ background: '#1e2235', color: '#e2e8f0', border: '1px solid #2a2d3e' }}>
<p className="whitespace-pre-wrap leading-relaxed">{entry.agent_response}</p>
</div>
{/* Activity collapsible */}
{hasActivity && (
<div className="mt-1.5 max-w-[80%]">
<button
onClick={() => setExpanded(v => !v)}
className="flex items-center gap-1.5 text-xs px-2 py-1 rounded-md transition-colors hover:bg-white/5"
style={{ color: '#6b7280' }}
>
<svg className={`w-3 h-3 transition-transform ${expanded ? 'rotate-90' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
</svg>
Activity ({entry.activity.length} steps)
</button>
{expanded && (
<div className="mt-1 px-3 py-2 rounded-lg space-y-0.5" style={{ background: '#0f1117', border: '1px solid #2a2d3e' }}>
{entry.activity.map(a => <ActivityEntry key={a.id} item={a} />)}
</div>
)}
</div>
)}
<p className="text-xs mt-1 ml-1" style={{ color: '#4b5563' }}>
{new Date(entry.timestamp).toLocaleTimeString()}
</p>
</div>
</div>
)
}
export default function ChatPanel({ agent, history, loading, onSend, onClearHistory }) {
const [input, setInput] = useState('')
const bottomRef = useRef(null)
useEffect(() => {
bottomRef.current?.scrollIntoView({ behavior: 'smooth' })
}, [history, loading])
const handleSubmit = (e) => {
e.preventDefault()
const msg = input.trim()
if (!msg || loading) return
setInput('')
onSend(msg)
}
return (
<div className="flex flex-col h-full">
{/* Clear button */}
<div className="flex justify-end px-4 py-2 shrink-0 border-b" style={{ borderColor: '#2a2d3e' }}>
<button
onClick={onClearHistory}
className="text-xs px-3 py-1.5 rounded-lg transition-colors hover:bg-white/5"
style={{ color: '#6b7280', border: '1px solid #2a2d3e' }}
>
Clear History
</button>
</div>
{/* Messages */}
<div className="flex-1 overflow-y-auto px-4 py-4">
{history.length === 0 && !loading && (
<div className="flex flex-col items-center justify-center h-full text-center text-gray-500">
<div className="w-14 h-14 rounded-2xl flex items-center justify-center mb-3 text-2xl" style={{ background: 'rgba(108,99,255,0.15)', border: '1px solid rgba(108,99,255,0.3)' }}>
🤖
</div>
<p className="font-medium text-gray-300">{agent.name}</p>
<p className="text-sm mt-1">{agent.role}</p>
<p className="text-xs mt-3 max-w-xs">Send a message to start the conversation</p>
</div>
)}
{history.map(entry => <MessageBubble key={entry.id} entry={entry} />)}
{loading && (
<div className="flex justify-start mb-4">
<div className="px-4 py-3 rounded-2xl rounded-tl-sm" style={{ background: '#1e2235', border: '1px solid #2a2d3e' }}>
<div className="flex gap-1.5 items-center">
<div className="w-2 h-2 rounded-full animate-bounce" style={{ background: '#6c63ff', animationDelay: '0ms' }} />
<div className="w-2 h-2 rounded-full animate-bounce" style={{ background: '#6c63ff', animationDelay: '150ms' }} />
<div className="w-2 h-2 rounded-full animate-bounce" style={{ background: '#6c63ff', animationDelay: '300ms' }} />
</div>
</div>
</div>
)}
<div ref={bottomRef} />
</div>
{/* Input */}
<form onSubmit={handleSubmit} className="px-4 py-3 shrink-0 border-t" style={{ borderColor: '#2a2d3e', background: '#1a1d27' }}>
<div className="flex gap-2">
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
placeholder={`Message ${agent.name}...`}
className="flex-1 px-4 py-2.5 rounded-xl text-sm outline-none transition-colors"
style={{ background: '#0f1117', border: '1px solid #2a2d3e', color: '#e2e8f0' }}
onFocus={e => e.target.style.borderColor = '#6c63ff'}
onBlur={e => e.target.style.borderColor = '#2a2d3e'}
disabled={loading}
/>
<button
type="submit"
disabled={loading || !input.trim()}
className="px-4 py-2.5 rounded-xl text-sm font-medium transition-all hover:opacity-90 disabled:opacity-40 disabled:cursor-not-allowed active:scale-95"
style={{ background: '#6c63ff', color: 'white' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
</svg>
</button>
</div>
</form>
</div>
)
}
+74
View File
@@ -0,0 +1,74 @@
export default function Sidebar({ agents, selectedId, onSelect, onNewAgent, onClose }) {
return (
<div className="flex flex-col h-full" style={{ background: '#1a1d27', borderRight: '1px solid #2a2d3e' }}>
{/* Header */}
<div className="flex items-center justify-between px-4 py-4 border-b shrink-0" style={{ borderColor: '#2a2d3e' }}>
<div className="flex items-center gap-2">
<div className="w-7 h-7 rounded-lg flex items-center justify-center text-white font-bold text-sm" style={{ background: '#6c63ff' }}>
A
</div>
<span className="font-bold text-white text-lg tracking-tight">AgentOS</span>
</div>
<button
onClick={onClose}
className="p-1.5 rounded-lg hover:bg-white/10 transition-colors md:hidden text-gray-400"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
{/* Agent list */}
<div className="flex-1 overflow-y-auto py-2 px-2">
<p className="px-2 py-1.5 text-xs font-semibold uppercase tracking-wider" style={{ color: '#6b7280' }}>
Agents ({agents.length})
</p>
{agents.length === 0 ? (
<div className="px-2 py-4 text-center text-sm text-gray-500">
No agents yet.<br />Create your first one below.
</div>
) : (
agents.map(agent => (
<button
key={agent.id}
onClick={() => onSelect(agent.id)}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left transition-all mb-0.5 group"
style={{
background: selectedId === agent.id ? 'rgba(108,99,255,0.15)' : 'transparent',
borderLeft: selectedId === agent.id ? '2px solid #6c63ff' : '2px solid transparent',
}}
>
<div
className="w-2 h-2 rounded-full shrink-0 mt-0.5"
style={{ background: agent.status === 'active' ? '#22c55e' : '#6b7280' }}
/>
<div className="flex-1 min-w-0">
<p className={`text-sm font-medium truncate ${selectedId === agent.id ? 'text-white' : 'text-gray-300 group-hover:text-white'}`}>
{agent.name}
</p>
<p className="text-xs truncate mt-0.5" style={{ color: '#6b7280' }}>
{agent.role}
</p>
</div>
</button>
))
)}
</div>
{/* New agent button */}
<div className="p-3 border-t shrink-0" style={{ borderColor: '#2a2d3e' }}>
<button
onClick={onNewAgent}
className="w-full flex items-center justify-center gap-2 px-4 py-2.5 rounded-lg text-sm font-medium transition-all hover:opacity-90 active:scale-95"
style={{ background: '#6c63ff', color: 'white' }}
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
New Agent
</button>
</div>
</div>
)
}
+9
View File
@@ -0,0 +1,9 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>
)
+18
View File
@@ -0,0 +1,18 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: {
host: '0.0.0.0',
port: 5000,
allowedHosts: true,
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
+6
View File
@@ -0,0 +1,6 @@
def main():
print("Hello from repl-nix-workspace!")
if __name__ == "__main__":
main()
BIN
View File
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
[project]
name = "repl-nix-workspace"
version = "0.1.0"
description = "Add your description here"
requires-python = ">=3.12"
dependencies = [
"aiosqlite>=0.22.1",
"fastapi>=0.136.0",
"httpx>=0.28.1",
"passlib[bcrypt]>=1.7.4",
"pydantic[email]>=2.13.2",
"pyjwt>=2.12.1",
"sqlalchemy>=2.0.49",
"uvicorn>=0.44.0",
]
+53
View File
@@ -0,0 +1,53 @@
# AgentOS — PraisonAI Agent Dashboard
## Overview
A full-stack web dashboard for managing and interacting with AI agents powered by PraisonAI.
## Architecture
### Frontend (React + Vite) — Port 5000
```
frontend/
index.html # Entry point, Tailwind CDN, Google Fonts
vite.config.js # Vite config, proxy /api → localhost:8000
src/
main.jsx # React root
App.jsx # Main app layout, state management
components/
Sidebar.jsx # Agent list + New Agent button
ChatPanel.jsx # Conversation with activity collapsible
AgentDetail.jsx # Agent config view + stats
ActivityLog.jsx # Real-time activity feed (right panel)
AgentForm.jsx # Create/edit agent modal
```
### Backend (FastAPI) — Port 8000
```
backend/
main.py # FastAPI app, JSON storage
agents_store.json # Agent definitions (persisted)
history_store.json # Conversation history (persisted)
```
## Running
- Frontend: `python run.py` (starts Vite dev server on port 5000)
- Backend: `python backend/main.py` (starts FastAPI on port 8000)
## API Endpoints
- `GET /agents` — list agents
- `POST /agents` — create agent
- `PUT /agents/{id}` — update agent
- `DELETE /agents/{id}` — delete agent
- `POST /agents/{id}/chat` — send message, get response + activity
- `GET /agents/{id}/history` — conversation history
- `DELETE /agents/{id}/history` — clear history
- `GET /agents/{id}/activity` — activity log
## Key Environment Variables
- `OPENAI_API_KEY` — enables real AI responses via praisonaiagents
(without it, demo mode is used)
## Platform API (legacy)
The original praisonai-platform FastAPI app is still available in:
- `src/praisonai-platform/` — multi-tenant workspace/issue tracking API
- `src/praisonai-agents/` — core praisonaiagents SDK
+11
View File
@@ -0,0 +1,11 @@
"""Start the AgentOS frontend dev server on port 5000."""
import os
import subprocess
import sys
if __name__ == "__main__":
os.chdir(os.path.join(os.path.dirname(__file__), "frontend"))
subprocess.run(
["npm", "run", "dev"],
env={**os.environ},
)
Generated
+508
View File
@@ -0,0 +1,508 @@
version = 1
revision = 3
requires-python = ">=3.12"
[[package]]
name = "aiosqlite"
version = "0.22.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4e/8a/64761f4005f17809769d23e518d915db74e6310474e733e3593cfc854ef1/aiosqlite-0.22.1.tar.gz", hash = "sha256:043e0bd78d32888c0a9ca90fc788b38796843360c855a7262a532813133a0650", size = 14821, upload-time = "2025-12-23T19:25:43.997Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/b7/e3bf5133d697a08128598c8d0abc5e16377b51465a33756de24fa7dee953/aiosqlite-0.22.1-py3-none-any.whl", hash = "sha256:21c002eb13823fad740196c5a2e9d8e62f6243bd9e7e4a1f87fb5e44ecb4fceb", size = 17405, upload-time = "2025-12-23T19:25:42.139Z" },
]
[[package]]
name = "annotated-doc"
version = "0.0.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
]
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
]
[[package]]
name = "bcrypt"
version = "5.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d4/36/3329e2518d70ad8e2e5817d5a4cac6bba05a47767ec416c7d020a965f408/bcrypt-5.0.0.tar.gz", hash = "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", size = 25386, upload-time = "2025-09-25T19:50:47.829Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/13/85/3e65e01985fddf25b64ca67275bb5bdb4040bd1a53b66d355c6c37c8a680/bcrypt-5.0.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", size = 481806, upload-time = "2025-09-25T19:49:05.102Z" },
{ url = "https://files.pythonhosted.org/packages/44/dc/01eb79f12b177017a726cbf78330eb0eb442fae0e7b3dfd84ea2849552f3/bcrypt-5.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", size = 268626, upload-time = "2025-09-25T19:49:06.723Z" },
{ url = "https://files.pythonhosted.org/packages/8c/cf/e82388ad5959c40d6afd94fb4743cc077129d45b952d46bdc3180310e2df/bcrypt-5.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", size = 271853, upload-time = "2025-09-25T19:49:08.028Z" },
{ url = "https://files.pythonhosted.org/packages/ec/86/7134b9dae7cf0efa85671651341f6afa695857fae172615e960fb6a466fa/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", size = 269793, upload-time = "2025-09-25T19:49:09.727Z" },
{ url = "https://files.pythonhosted.org/packages/cc/82/6296688ac1b9e503d034e7d0614d56e80c5d1a08402ff856a4549cb59207/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", size = 289930, upload-time = "2025-09-25T19:49:11.204Z" },
{ url = "https://files.pythonhosted.org/packages/d1/18/884a44aa47f2a3b88dd09bc05a1e40b57878ecd111d17e5bba6f09f8bb77/bcrypt-5.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", size = 272194, upload-time = "2025-09-25T19:49:12.524Z" },
{ url = "https://files.pythonhosted.org/packages/0e/8f/371a3ab33c6982070b674f1788e05b656cfbf5685894acbfef0c65483a59/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", size = 269381, upload-time = "2025-09-25T19:49:14.308Z" },
{ url = "https://files.pythonhosted.org/packages/b1/34/7e4e6abb7a8778db6422e88b1f06eb07c47682313997ee8a8f9352e5a6f1/bcrypt-5.0.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", size = 271750, upload-time = "2025-09-25T19:49:15.584Z" },
{ url = "https://files.pythonhosted.org/packages/c0/1b/54f416be2499bd72123c70d98d36c6cd61a4e33d9b89562c22481c81bb30/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", size = 303757, upload-time = "2025-09-25T19:49:17.244Z" },
{ url = "https://files.pythonhosted.org/packages/13/62/062c24c7bcf9d2826a1a843d0d605c65a755bc98002923d01fd61270705a/bcrypt-5.0.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", size = 306740, upload-time = "2025-09-25T19:49:18.693Z" },
{ url = "https://files.pythonhosted.org/packages/d5/c8/1fdbfc8c0f20875b6b4020f3c7dc447b8de60aa0be5faaf009d24242aec9/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", size = 334197, upload-time = "2025-09-25T19:49:20.523Z" },
{ url = "https://files.pythonhosted.org/packages/a6/c1/8b84545382d75bef226fbc6588af0f7b7d095f7cd6a670b42a86243183cd/bcrypt-5.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", size = 352974, upload-time = "2025-09-25T19:49:22.254Z" },
{ url = "https://files.pythonhosted.org/packages/10/a6/ffb49d4254ed085e62e3e5dd05982b4393e32fe1e49bb1130186617c29cd/bcrypt-5.0.0-cp313-cp313t-win32.whl", hash = "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", size = 148498, upload-time = "2025-09-25T19:49:24.134Z" },
{ url = "https://files.pythonhosted.org/packages/48/a9/259559edc85258b6d5fc5471a62a3299a6aa37a6611a169756bf4689323c/bcrypt-5.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", size = 145853, upload-time = "2025-09-25T19:49:25.702Z" },
{ url = "https://files.pythonhosted.org/packages/2d/df/9714173403c7e8b245acf8e4be8876aac64a209d1b392af457c79e60492e/bcrypt-5.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", size = 139626, upload-time = "2025-09-25T19:49:26.928Z" },
{ url = "https://files.pythonhosted.org/packages/f8/14/c18006f91816606a4abe294ccc5d1e6f0e42304df5a33710e9e8e95416e1/bcrypt-5.0.0-cp314-cp314t-macosx_10_12_universal2.whl", hash = "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", size = 481862, upload-time = "2025-09-25T19:49:28.365Z" },
{ url = "https://files.pythonhosted.org/packages/67/49/dd074d831f00e589537e07a0725cf0e220d1f0d5d8e85ad5bbff251c45aa/bcrypt-5.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", size = 268544, upload-time = "2025-09-25T19:49:30.39Z" },
{ url = "https://files.pythonhosted.org/packages/f5/91/50ccba088b8c474545b034a1424d05195d9fcbaaf802ab8bfe2be5a4e0d7/bcrypt-5.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", size = 271787, upload-time = "2025-09-25T19:49:32.144Z" },
{ url = "https://files.pythonhosted.org/packages/aa/e7/d7dba133e02abcda3b52087a7eea8c0d4f64d3e593b4fffc10c31b7061f3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", size = 269753, upload-time = "2025-09-25T19:49:33.885Z" },
{ url = "https://files.pythonhosted.org/packages/33/fc/5b145673c4b8d01018307b5c2c1fc87a6f5a436f0ad56607aee389de8ee3/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", size = 289587, upload-time = "2025-09-25T19:49:35.144Z" },
{ url = "https://files.pythonhosted.org/packages/27/d7/1ff22703ec6d4f90e62f1a5654b8867ef96bafb8e8102c2288333e1a6ca6/bcrypt-5.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", size = 272178, upload-time = "2025-09-25T19:49:36.793Z" },
{ url = "https://files.pythonhosted.org/packages/c8/88/815b6d558a1e4d40ece04a2f84865b0fef233513bd85fd0e40c294272d62/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", size = 269295, upload-time = "2025-09-25T19:49:38.164Z" },
{ url = "https://files.pythonhosted.org/packages/51/8c/e0db387c79ab4931fc89827d37608c31cc57b6edc08ccd2386139028dc0d/bcrypt-5.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", size = 271700, upload-time = "2025-09-25T19:49:39.917Z" },
{ url = "https://files.pythonhosted.org/packages/06/83/1570edddd150f572dbe9fc00f6203a89fc7d4226821f67328a85c330f239/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", size = 334034, upload-time = "2025-09-25T19:49:41.227Z" },
{ url = "https://files.pythonhosted.org/packages/c9/f2/ea64e51a65e56ae7a8a4ec236c2bfbdd4b23008abd50ac33fbb2d1d15424/bcrypt-5.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", size = 352766, upload-time = "2025-09-25T19:49:43.08Z" },
{ url = "https://files.pythonhosted.org/packages/d7/d4/1a388d21ee66876f27d1a1f41287897d0c0f1712ef97d395d708ba93004c/bcrypt-5.0.0-cp314-cp314t-win32.whl", hash = "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", size = 152449, upload-time = "2025-09-25T19:49:44.971Z" },
{ url = "https://files.pythonhosted.org/packages/3f/61/3291c2243ae0229e5bca5d19f4032cecad5dfb05a2557169d3a69dc0ba91/bcrypt-5.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", size = 149310, upload-time = "2025-09-25T19:49:46.162Z" },
{ url = "https://files.pythonhosted.org/packages/3e/89/4b01c52ae0c1a681d4021e5dd3e45b111a8fb47254a274fa9a378d8d834b/bcrypt-5.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", size = 143761, upload-time = "2025-09-25T19:49:47.345Z" },
{ url = "https://files.pythonhosted.org/packages/84/29/6237f151fbfe295fe3e074ecc6d44228faa1e842a81f6d34a02937ee1736/bcrypt-5.0.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b", size = 494553, upload-time = "2025-09-25T19:49:49.006Z" },
{ url = "https://files.pythonhosted.org/packages/45/b6/4c1205dde5e464ea3bd88e8742e19f899c16fa8916fb8510a851fae985b5/bcrypt-5.0.0-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", size = 275009, upload-time = "2025-09-25T19:49:50.581Z" },
{ url = "https://files.pythonhosted.org/packages/3b/71/427945e6ead72ccffe77894b2655b695ccf14ae1866cd977e185d606dd2f/bcrypt-5.0.0-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", size = 278029, upload-time = "2025-09-25T19:49:52.533Z" },
{ url = "https://files.pythonhosted.org/packages/17/72/c344825e3b83c5389a369c8a8e58ffe1480b8a699f46c127c34580c4666b/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", size = 275907, upload-time = "2025-09-25T19:49:54.709Z" },
{ url = "https://files.pythonhosted.org/packages/0b/7e/d4e47d2df1641a36d1212e5c0514f5291e1a956a7749f1e595c07a972038/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", size = 296500, upload-time = "2025-09-25T19:49:56.013Z" },
{ url = "https://files.pythonhosted.org/packages/0f/c3/0ae57a68be2039287ec28bc463b82e4b8dc23f9d12c0be331f4782e19108/bcrypt-5.0.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", size = 278412, upload-time = "2025-09-25T19:49:57.356Z" },
{ url = "https://files.pythonhosted.org/packages/45/2b/77424511adb11e6a99e3a00dcc7745034bee89036ad7d7e255a7e47be7d8/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", size = 275486, upload-time = "2025-09-25T19:49:59.116Z" },
{ url = "https://files.pythonhosted.org/packages/43/0a/405c753f6158e0f3f14b00b462d8bca31296f7ecfc8fc8bc7919c0c7d73a/bcrypt-5.0.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", size = 277940, upload-time = "2025-09-25T19:50:00.869Z" },
{ url = "https://files.pythonhosted.org/packages/62/83/b3efc285d4aadc1fa83db385ec64dcfa1707e890eb42f03b127d66ac1b7b/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", size = 310776, upload-time = "2025-09-25T19:50:02.393Z" },
{ url = "https://files.pythonhosted.org/packages/95/7d/47ee337dacecde6d234890fe929936cb03ebc4c3a7460854bbd9c97780b8/bcrypt-5.0.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", size = 312922, upload-time = "2025-09-25T19:50:04.232Z" },
{ url = "https://files.pythonhosted.org/packages/d6/3a/43d494dfb728f55f4e1cf8fd435d50c16a2d75493225b54c8d06122523c6/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", size = 341367, upload-time = "2025-09-25T19:50:05.559Z" },
{ url = "https://files.pythonhosted.org/packages/55/ab/a0727a4547e383e2e22a630e0f908113db37904f58719dc48d4622139b5c/bcrypt-5.0.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", size = 359187, upload-time = "2025-09-25T19:50:06.916Z" },
{ url = "https://files.pythonhosted.org/packages/1b/bb/461f352fdca663524b4643d8b09e8435b4990f17fbf4fea6bc2a90aa0cc7/bcrypt-5.0.0-cp38-abi3-win32.whl", hash = "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", size = 153752, upload-time = "2025-09-25T19:50:08.515Z" },
{ url = "https://files.pythonhosted.org/packages/41/aa/4190e60921927b7056820291f56fc57d00d04757c8b316b2d3c0d1d6da2c/bcrypt-5.0.0-cp38-abi3-win_amd64.whl", hash = "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", size = 150881, upload-time = "2025-09-25T19:50:09.742Z" },
{ url = "https://files.pythonhosted.org/packages/54/12/cd77221719d0b39ac0b55dbd39358db1cd1246e0282e104366ebbfb8266a/bcrypt-5.0.0-cp38-abi3-win_arm64.whl", hash = "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", size = 144931, upload-time = "2025-09-25T19:50:11.016Z" },
{ url = "https://files.pythonhosted.org/packages/5d/ba/2af136406e1c3839aea9ecadc2f6be2bcd1eff255bd451dd39bcf302c47a/bcrypt-5.0.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", size = 495313, upload-time = "2025-09-25T19:50:12.309Z" },
{ url = "https://files.pythonhosted.org/packages/ac/ee/2f4985dbad090ace5ad1f7dd8ff94477fe089b5fab2040bd784a3d5f187b/bcrypt-5.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", size = 275290, upload-time = "2025-09-25T19:50:13.673Z" },
{ url = "https://files.pythonhosted.org/packages/e4/6e/b77ade812672d15cf50842e167eead80ac3514f3beacac8902915417f8b7/bcrypt-5.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", size = 278253, upload-time = "2025-09-25T19:50:15.089Z" },
{ url = "https://files.pythonhosted.org/packages/36/c4/ed00ed32f1040f7990dac7115f82273e3c03da1e1a1587a778d8cea496d8/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", size = 276084, upload-time = "2025-09-25T19:50:16.699Z" },
{ url = "https://files.pythonhosted.org/packages/e7/c4/fa6e16145e145e87f1fa351bbd54b429354fd72145cd3d4e0c5157cf4c70/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", size = 297185, upload-time = "2025-09-25T19:50:18.525Z" },
{ url = "https://files.pythonhosted.org/packages/24/b4/11f8a31d8b67cca3371e046db49baa7c0594d71eb40ac8121e2fc0888db0/bcrypt-5.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", size = 278656, upload-time = "2025-09-25T19:50:19.809Z" },
{ url = "https://files.pythonhosted.org/packages/ac/31/79f11865f8078e192847d2cb526e3fa27c200933c982c5b2869720fa5fce/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", size = 275662, upload-time = "2025-09-25T19:50:21.567Z" },
{ url = "https://files.pythonhosted.org/packages/d4/8d/5e43d9584b3b3591a6f9b68f755a4da879a59712981ef5ad2a0ac1379f7a/bcrypt-5.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", size = 278240, upload-time = "2025-09-25T19:50:23.305Z" },
{ url = "https://files.pythonhosted.org/packages/89/48/44590e3fc158620f680a978aafe8f87a4c4320da81ed11552f0323aa9a57/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", size = 311152, upload-time = "2025-09-25T19:50:24.597Z" },
{ url = "https://files.pythonhosted.org/packages/5f/85/e4fbfc46f14f47b0d20493669a625da5827d07e8a88ee460af6cd9768b44/bcrypt-5.0.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", size = 313284, upload-time = "2025-09-25T19:50:26.268Z" },
{ url = "https://files.pythonhosted.org/packages/25/ae/479f81d3f4594456a01ea2f05b132a519eff9ab5768a70430fa1132384b1/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", size = 341643, upload-time = "2025-09-25T19:50:28.02Z" },
{ url = "https://files.pythonhosted.org/packages/df/d2/36a086dee1473b14276cd6ea7f61aef3b2648710b5d7f1c9e032c29b859f/bcrypt-5.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", size = 359698, upload-time = "2025-09-25T19:50:31.347Z" },
{ url = "https://files.pythonhosted.org/packages/c0/f6/688d2cd64bfd0b14d805ddb8a565e11ca1fb0fd6817175d58b10052b6d88/bcrypt-5.0.0-cp39-abi3-win32.whl", hash = "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", size = 153725, upload-time = "2025-09-25T19:50:34.384Z" },
{ url = "https://files.pythonhosted.org/packages/9f/b9/9d9a641194a730bda138b3dfe53f584d61c58cd5230e37566e83ec2ffa0d/bcrypt-5.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", size = 150912, upload-time = "2025-09-25T19:50:35.69Z" },
{ url = "https://files.pythonhosted.org/packages/27/44/d2ef5e87509158ad2187f4dd0852df80695bb1ee0cfe0a684727b01a69e0/bcrypt-5.0.0-cp39-abi3-win_arm64.whl", hash = "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", size = 144953, upload-time = "2025-09-25T19:50:37.32Z" },
]
[[package]]
name = "certifi"
version = "2026.2.25"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
]
[[package]]
name = "click"
version = "8.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "dnspython"
version = "2.8.0"
source = { registry = "https://pypi.org/simple" }
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" }
wheels = [
{ 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" },
]
[[package]]
name = "email-validator"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "dnspython" },
{ name = "idna" },
]
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" }
wheels = [
{ 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" },
]
[[package]]
name = "fastapi"
version = "0.136.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
{ name = "pydantic" },
{ name = "starlette" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4e/d9/e66315807e41e69e7f6a1b42a162dada2f249c5f06ad3f1a95f84ab336ef/fastapi-0.136.0.tar.gz", hash = "sha256:cf08e067cc66e106e102d9ba659463abfac245200752f8a5b7b1e813de4ff73e", size = 396607, upload-time = "2026-04-16T11:47:13.623Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/26/a3/0bd5f0cdb0bbc92650e8dc457e9250358411ee5d1b65e42b6632387daf81/fastapi-0.136.0-py3-none-any.whl", hash = "sha256:8793d44ec7378e2be07f8a013cf7f7aa47d6327d0dfe9804862688ec4541a6b4", size = 117556, upload-time = "2026-04-16T11:47:11.922Z" },
]
[[package]]
name = "greenlet"
version = "3.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/86/94/a5935717b307d7c71fe877b52b884c6af707d2d2090db118a03fbd799369/greenlet-3.4.0.tar.gz", hash = "sha256:f50a96b64dafd6169e595a5c56c9146ef80333e67d4476a65a9c55f400fc22ff", size = 195913, upload-time = "2026-04-08T17:08:00.863Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/65/8b/3669ad3b3f247a791b2b4aceb3aa5a31f5f6817bf547e4e1ff712338145a/greenlet-3.4.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:1a54a921561dd9518d31d2d3db4d7f80e589083063ab4d3e2e950756ef809e1a", size = 286902, upload-time = "2026-04-08T15:52:12.138Z" },
{ url = "https://files.pythonhosted.org/packages/38/3e/3c0e19b82900873e2d8469b590a6c4b3dfd2b316d0591f1c26b38a4879a5/greenlet-3.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:16dec271460a9a2b154e3b1c2fa1050ce6280878430320e85e08c166772e3f97", size = 606099, upload-time = "2026-04-08T16:24:38.408Z" },
{ url = "https://files.pythonhosted.org/packages/b5/33/99fef65e7754fc76a4ed14794074c38c9ed3394a5bd129d7f61b705f3168/greenlet-3.4.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:90036ce224ed6fe75508c1907a77e4540176dcf0744473627785dd519c6f9996", size = 618837, upload-time = "2026-04-08T16:30:58.298Z" },
{ url = "https://files.pythonhosted.org/packages/44/57/eae2cac10421feae6c0987e3dc106c6d86262b1cb379e171b017aba893a6/greenlet-3.4.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6f0def07ec9a71d72315cf26c061aceee53b306c36ed38c35caba952ea1b319d", size = 624901, upload-time = "2026-04-08T16:40:38.981Z" },
{ url = "https://files.pythonhosted.org/packages/36/f7/229f3aed6948faa20e0616a0b8568da22e365ede6a54d7d369058b128afd/greenlet-3.4.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a1c4f6b453006efb8310affb2d132832e9bbb4fc01ce6df6b70d810d38f1f6dc", size = 615062, upload-time = "2026-04-08T15:56:33.766Z" },
{ url = "https://files.pythonhosted.org/packages/6a/8a/0e73c9b94f31d1cc257fe79a0eff621674141cdae7d6d00f40de378a1e42/greenlet-3.4.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:0e1254cf0cbaa17b04320c3a78575f29f3c161ef38f59c977108f19ffddaf077", size = 423927, upload-time = "2026-04-08T16:43:05.293Z" },
{ url = "https://files.pythonhosted.org/packages/08/97/d988180011aa40135c46cd0d0cf01dd97f7162bae14139b4a3ef54889ba5/greenlet-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9b2d9a138ffa0e306d0e2b72976d2fb10b97e690d40ab36a472acaab0838e2de", size = 1573511, upload-time = "2026-04-08T16:26:20.058Z" },
{ url = "https://files.pythonhosted.org/packages/d4/0f/a5a26fe152fb3d12e6a474181f6e9848283504d0afd095f353d85726374b/greenlet-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8424683caf46eb0eb6f626cb95e008e8cc30d0cb675bdfa48200925c79b38a08", size = 1640396, upload-time = "2026-04-08T15:57:30.88Z" },
{ url = "https://files.pythonhosted.org/packages/42/cf/bb2c32d9a100e36ee9f6e38fad6b1e082b8184010cb06259b49e1266ca01/greenlet-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0a53fb071531d003b075c444014ff8f8b1a9898d36bb88abd9ac7b3524648a2", size = 238892, upload-time = "2026-04-08T17:03:10.094Z" },
{ url = "https://files.pythonhosted.org/packages/b7/47/6c41314bac56e71436ce551c7fbe3cc830ed857e6aa9708dbb9c65142eb6/greenlet-3.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:f38b81880ba28f232f1f675893a39cf7b6db25b31cc0a09bb50787ecf957e85e", size = 235599, upload-time = "2026-04-08T15:52:54.3Z" },
{ url = "https://files.pythonhosted.org/packages/7a/75/7e9cd1126a1e1f0cd67b0eda02e5221b28488d352684704a78ed505bd719/greenlet-3.4.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:43748988b097f9c6f09364f260741aa73c80747f63389824435c7a50bfdfd5c1", size = 285856, upload-time = "2026-04-08T15:52:45.82Z" },
{ url = "https://files.pythonhosted.org/packages/9d/c4/3e2df392e5cb199527c4d9dbcaa75c14edcc394b45040f0189f649631e3c/greenlet-3.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5566e4e2cd7a880e8c27618e3eab20f3494452d12fd5129edef7b2f7aa9a36d1", size = 610208, upload-time = "2026-04-08T16:24:39.674Z" },
{ url = "https://files.pythonhosted.org/packages/da/af/750cdfda1d1bd30a6c28080245be8d0346e669a98fdbae7f4102aa95fff3/greenlet-3.4.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1054c5a3c78e2ab599d452f23f7adafef55062a783a8e241d24f3b633ba6ff82", size = 621269, upload-time = "2026-04-08T16:30:59.767Z" },
{ url = "https://files.pythonhosted.org/packages/e0/93/c8c508d68ba93232784bbc1b5474d92371f2897dfc6bc281b419f2e0d492/greenlet-3.4.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:98eedd1803353daf1cd9ef23eef23eda5a4d22f99b1f998d273a8b78b70dd47f", size = 628455, upload-time = "2026-04-08T16:40:40.698Z" },
{ url = "https://files.pythonhosted.org/packages/54/78/0cbc693622cd54ebe25207efbb3a0eb07c2639cb8594f6e3aaaa0bb077a8/greenlet-3.4.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f82cb6cddc27dd81c96b1506f4aa7def15070c3b2a67d4e46fd19016aacce6cf", size = 617549, upload-time = "2026-04-08T15:56:34.893Z" },
{ url = "https://files.pythonhosted.org/packages/7f/46/cfaaa0ade435a60550fd83d07dfd5c41f873a01da17ede5c4cade0b9bab8/greenlet-3.4.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:b7857e2202aae67bc5725e0c1f6403c20a8ff46094ece015e7d474f5f7020b55", size = 426238, upload-time = "2026-04-08T16:43:06.865Z" },
{ url = "https://files.pythonhosted.org/packages/ba/c0/8966767de01343c1ff47e8b855dc78e7d1a8ed2b7b9c83576a57e289f81d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:227a46251ecba4ff46ae742bc5ce95c91d5aceb4b02f885487aff269c127a729", size = 1575310, upload-time = "2026-04-08T16:26:21.671Z" },
{ url = "https://files.pythonhosted.org/packages/b8/38/bcdc71ba05e9a5fda87f63ffc2abcd1f15693b659346df994a48c968003d/greenlet-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5b99e87be7eba788dd5b75ba1cde5639edffdec5f91fe0d734a249535ec3408c", size = 1640435, upload-time = "2026-04-08T15:57:32.572Z" },
{ url = "https://files.pythonhosted.org/packages/a1/c2/19b664b7173b9e4ef5f77e8cef9f14c20ec7fce7920dc1ccd7afd955d093/greenlet-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:849f8bc17acd6295fcb5de8e46d55cc0e52381c56eaf50a2afd258e97bc65940", size = 238760, upload-time = "2026-04-08T17:04:03.878Z" },
{ url = "https://files.pythonhosted.org/packages/9b/96/795619651d39c7fbd809a522f881aa6f0ead504cc8201c3a5b789dfaef99/greenlet-3.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:9390ad88b652b1903814eaabd629ca184db15e0eeb6fe8a390bbf8b9106ae15a", size = 235498, upload-time = "2026-04-08T17:05:00.584Z" },
{ url = "https://files.pythonhosted.org/packages/78/02/bde66806e8f169cf90b14d02c500c44cdbe02c8e224c9c67bafd1b8cadd1/greenlet-3.4.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:10a07aca6babdd18c16a3f4f8880acfffc2b88dfe431ad6aa5f5740759d7d75e", size = 286291, upload-time = "2026-04-08T17:09:34.307Z" },
{ url = "https://files.pythonhosted.org/packages/05/1f/39da1c336a87d47c58352fb8a78541ce63d63ae57c5b9dae1fe02801bbc2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:076e21040b3a917d3ce4ad68fb5c3c6b32f1405616c4a57aa83120979649bd3d", size = 656749, upload-time = "2026-04-08T16:24:41.721Z" },
{ url = "https://files.pythonhosted.org/packages/d3/6c/90ee29a4ee27af7aa2e2ec408799eeb69ee3fcc5abcecac6ddd07a5cd0f2/greenlet-3.4.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e82689eea4a237e530bb5cb41b180ef81fa2160e1f89422a67be7d90da67f615", size = 669084, upload-time = "2026-04-08T16:31:01.372Z" },
{ url = "https://files.pythonhosted.org/packages/d2/4a/74078d3936712cff6d3c91a930016f476ce4198d84e224fe6d81d3e02880/greenlet-3.4.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:06c2d3b89e0c62ba50bd7adf491b14f39da9e7e701647cb7b9ff4c99bee04b19", size = 673405, upload-time = "2026-04-08T16:40:42.527Z" },
{ url = "https://files.pythonhosted.org/packages/07/49/d4cad6e5381a50947bb973d2f6cf6592621451b09368b8c20d9b8af49c5b/greenlet-3.4.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4df3b0b2289ec686d3c821a5fee44259c05cfe824dd5e6e12c8e5f5df23085cf", size = 665621, upload-time = "2026-04-08T15:56:35.995Z" },
{ url = "https://files.pythonhosted.org/packages/79/3e/df8a83ab894751bc31e1106fdfaa80ca9753222f106b04de93faaa55feb7/greenlet-3.4.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:070b8bac2ff3b4d9e0ff36a0d19e42103331d9737e8504747cd1e659f76297bd", size = 471670, upload-time = "2026-04-08T16:43:08.512Z" },
{ url = "https://files.pythonhosted.org/packages/37/31/d1edd54f424761b5d47718822f506b435b6aab2f3f93b465441143ea5119/greenlet-3.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8bff29d586ea415688f4cec96a591fcc3bf762d046a796cdadc1fdb6e7f2d5bf", size = 1622259, upload-time = "2026-04-08T16:26:23.201Z" },
{ url = "https://files.pythonhosted.org/packages/b0/c6/6d3f9cdcb21c4e12a79cb332579f1c6aa1af78eb68059c5a957c7812d95e/greenlet-3.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8a569c2fb840c53c13a2b8967c63621fafbd1a0e015b9c82f408c33d626a2fda", size = 1686916, upload-time = "2026-04-08T15:57:34.282Z" },
{ url = "https://files.pythonhosted.org/packages/63/45/c1ca4a1ad975de4727e52d3ffe641ae23e1d7a8ffaa8ff7a0477e1827b92/greenlet-3.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:207ba5b97ea8b0b60eb43ffcacf26969dd83726095161d676aac03ff913ee50d", size = 239821, upload-time = "2026-04-08T17:03:48.423Z" },
{ url = "https://files.pythonhosted.org/packages/71/c4/6f621023364d7e85a4769c014c8982f98053246d142420e0328980933ceb/greenlet-3.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:f8296d4e2b92af34ebde81085a01690f26a51eb9ac09a0fcadb331eb36dbc802", size = 236932, upload-time = "2026-04-08T17:04:33.551Z" },
{ url = "https://files.pythonhosted.org/packages/d4/8f/18d72b629783f5e8d045a76f5325c1e938e659a9e4da79c7dcd10169a48d/greenlet-3.4.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d70012e51df2dbbccfaf63a40aaf9b40c8bed37c3e3a38751c926301ce538ece", size = 294681, upload-time = "2026-04-08T15:52:35.778Z" },
{ url = "https://files.pythonhosted.org/packages/9e/ad/5fa86ec46769c4153820d58a04062285b3b9e10ba3d461ee257b68dcbf53/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a58bec0751f43068cd40cff31bb3ca02ad6000b3a51ca81367af4eb5abc480c8", size = 658899, upload-time = "2026-04-08T16:24:43.32Z" },
{ url = "https://files.pythonhosted.org/packages/43/f0/4e8174ca0e87ae748c409f055a1ba161038c43cc0a5a6f1433a26ac2e5bf/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:05fa0803561028f4b2e3b490ee41216a842eaee11aed004cc343a996d9523aa2", size = 665284, upload-time = "2026-04-08T16:31:02.833Z" },
{ url = "https://files.pythonhosted.org/packages/ef/92/466b0d9afd44b8af623139a3599d651c7564fa4152f25f117e1ee5949ffb/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c4cd56a9eb7a6444edbc19062f7b6fbc8f287c663b946e3171d899693b1c19fa", size = 665872, upload-time = "2026-04-08T16:40:43.912Z" },
{ url = "https://files.pythonhosted.org/packages/19/da/991cf7cd33662e2df92a1274b7eb4d61769294d38a1bba8a45f31364845e/greenlet-3.4.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e60d38719cb80b3ab5e85f9f1aed4960acfde09868af6762ccb27b260d68f4ed", size = 661861, upload-time = "2026-04-08T15:56:37.269Z" },
{ url = "https://files.pythonhosted.org/packages/0d/14/3395a7ef3e260de0325152ddfe19dffb3e49fe10873b94654352b53ad48e/greenlet-3.4.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:1f85f204c4d54134ae850d401fa435c89cd667d5ce9dc567571776b45941af72", size = 489237, upload-time = "2026-04-08T16:43:09.993Z" },
{ url = "https://files.pythonhosted.org/packages/36/c5/6c2c708e14db3d9caea4b459d8464f58c32047451142fe2cfd90e7458f41/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7f50c804733b43eded05ae694691c9aa68bca7d0a867d67d4a3f514742a2d53f", size = 1622182, upload-time = "2026-04-08T16:26:24.777Z" },
{ url = "https://files.pythonhosted.org/packages/7a/4c/50c5fed19378e11a29fabab1f6be39ea95358f4a0a07e115a51ca93385d8/greenlet-3.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:2d4f0635dc4aa638cda4b2f5a07ae9a2cff9280327b581a3fcb6f317b4fbc38a", size = 1685050, upload-time = "2026-04-08T15:57:36.453Z" },
{ url = "https://files.pythonhosted.org/packages/db/72/85ae954d734703ab48e622c59d4ce35d77ce840c265814af9c078cacc7aa/greenlet-3.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1a4a48f24681300c640f143ba7c404270e1ebbbcf34331d7104a4ff40f8ea705", size = 245554, upload-time = "2026-04-08T17:03:50.044Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "passlib"
version = "1.7.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/06/9da9ee59a67fae7761aab3ccc84fa4f3f33f125b370f1ccdb915bf967c11/passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04", size = 689844, upload-time = "2020-10-08T19:00:52.121Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3b/a4/ab6b7589382ca3df236e03faa71deac88cae040af60c071a78d254a62172/passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1", size = 525554, upload-time = "2020-10-08T19:00:49.856Z" },
]
[package.optional-dependencies]
bcrypt = [
{ name = "bcrypt" },
]
[[package]]
name = "pydantic"
version = "2.13.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/09/e5/06d23afac9973109d1e3c8ad38e1547a12e860610e327c05ee686827dc37/pydantic-2.13.2.tar.gz", hash = "sha256:b418196607e61081c3226dcd4f0672f2a194828abb9109e9cfb84026564df2d1", size = 843836, upload-time = "2026-04-17T09:31:59.636Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/77/ca/b45c378e6e8d0b90577288b533e04e95b7afd61bb1d51b6c263176435489/pydantic-2.13.2-py3-none-any.whl", hash = "sha256:a525087f4c03d7e7456a3de89b64cd693d2229933bb1068b9af6befd5563694e", size = 471947, upload-time = "2026-04-17T09:31:57.541Z" },
]
[package.optional-dependencies]
email = [
{ name = "email-validator" },
]
[[package]]
name = "pydantic-core"
version = "2.46.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/bb/4742f05b739b2478459bb16fa8470549518c802e06ddcf3f106c5081315e/pydantic_core-2.46.2.tar.gz", hash = "sha256:37bb079f9ee3f1a519392b73fda2a96379b31f2013c6b467fe693e7f2987f596", size = 471269, upload-time = "2026-04-17T09:10:07.017Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/97/ec/2fafa4c86f5d2a69372c7cddef30925fd0e370b1efaf556609c1a0196d8a/pydantic_core-2.46.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ea1ad8c89da31512fe2d249cf0638fb666925bda341901541bc5f3311c6fcc9e", size = 2101729, upload-time = "2026-04-17T09:12:30.042Z" },
{ url = "https://files.pythonhosted.org/packages/cf/55/be5386c2c4b49af346e8a26b748194ff25757bbb6cf544130854e997af7a/pydantic_core-2.46.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b308da17b92481e0587244631c5529e5d91d04cb2b08194825627b1eca28e21e", size = 1951546, upload-time = "2026-04-17T09:10:10.585Z" },
{ url = "https://files.pythonhosted.org/packages/29/92/89e273a055ce440e6636c756379af35ad86da9d336a560049c3ba5e41c80/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d333a50bdd814a917d8d6a7ee35ba2395d53ddaa882613bc24e54a9d8b129095", size = 1976178, upload-time = "2026-04-17T09:11:49.619Z" },
{ url = "https://files.pythonhosted.org/packages/91/b3/e4664469cf70c0cb0f7b2f5719d64e5968bb6f38217042c2afa3d3c4ba17/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d00b99590c5bd1fabbc5d28b170923e32c1b1071b1f1de1851a4d14d89eb192", size = 2051697, upload-time = "2026-04-17T09:12:04.917Z" },
{ url = "https://files.pythonhosted.org/packages/98/58/dbf68213ee06ce51cdd6d8c95f97980e646858c45bd96bd2dfb40433be73/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9f0e686960ffe9e65066395af856ac2d52c159043144433602c50c221d81c1ba", size = 2233160, upload-time = "2026-04-17T09:12:00.956Z" },
{ url = "https://files.pythonhosted.org/packages/f5/d3/68092aa0ee6c60ff4de4740eb82db3d4ce338ec89b3cecb978c532472f12/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d1128da41c9cb474e0a4701f9c363ec645c9d1a02229904c76bf4e0a194fde2", size = 2298398, upload-time = "2026-04-17T09:10:29.694Z" },
{ url = "https://files.pythonhosted.org/packages/e4/51/5d6155eb737db55b0ad354ca5f333ef009f75feb67df2d79a84bace45af6/pydantic_core-2.46.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48649cf2d8c358d79586e9fb2f8235902fcaa2d969ec1c5301f2d1873b2f8321", size = 2094058, upload-time = "2026-04-17T09:12:10.995Z" },
{ url = "https://files.pythonhosted.org/packages/6b/f3/eb4a986197d71319430464ff181226c95adc8f06d932189b158bae5a82f5/pydantic_core-2.46.2-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:b902f0fc7c2cf503865a05718b68147c6cd5d0a3867af38c527be574a9fa6e9d", size = 2130388, upload-time = "2026-04-17T09:12:41.159Z" },
{ url = "https://files.pythonhosted.org/packages/56/00/44a9c4fe6d0f64b5786d6a8c649d6f0e34ba6c89b3663add1066e54451a2/pydantic_core-2.46.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e80011f808b03d1d87a8f1e76ae3da19a18eb706c823e17981dcf1fae43744fc", size = 2184245, upload-time = "2026-04-17T09:12:36.532Z" },
{ url = "https://files.pythonhosted.org/packages/78/6b/685b98a834d5e3d1c34a1bde1627525559dd223b75075bc7490cdb24eb33/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b839d5c802e31348b949b6473f8190cddbf7d47475856d8ac995a373ee16ec59", size = 2186842, upload-time = "2026-04-17T09:13:04.054Z" },
{ url = "https://files.pythonhosted.org/packages/22/64/caa2f5a2ac8b6113adaa410ccdf31ba7f54897a6e54cd0d726fc7e780c88/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:c6b1064f3f9cf9072e1d59dd2936f9f3b668bec1c37039708c9222db703c0d5b", size = 2336066, upload-time = "2026-04-17T09:12:13.006Z" },
{ url = "https://files.pythonhosted.org/packages/ee/f9/7d2701bf82945b5b9e7df8347be97ef6a36da2846bfe5b4afec299ffe27b/pydantic_core-2.46.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a68e6f2ac95578ce3c0564802404b27b24988649616e556c07e77111ed3f1d", size = 2363691, upload-time = "2026-04-17T09:13:42.972Z" },
{ url = "https://files.pythonhosted.org/packages/3b/65/0dab11574101522941055109419db3cc09db871643dc3fc74e2413215e5b/pydantic_core-2.46.2-cp312-cp312-win32.whl", hash = "sha256:d9ffa75a7ef4b97d6e5e205fabd4304ef01fec09e6f1bdde04b9ad1b07d20289", size = 1958801, upload-time = "2026-04-17T09:11:31.981Z" },
{ url = "https://files.pythonhosted.org/packages/13/2b/df84baa609c676f6450b8ecad44ea59146c805e3371b7b52443c0899f989/pydantic_core-2.46.2-cp312-cp312-win_amd64.whl", hash = "sha256:0551f2d2ddb68af5a00e26497f8025c538f73ef3cb698f8e5a487042cd2792a8", size = 2072634, upload-time = "2026-04-17T09:11:02.407Z" },
{ url = "https://files.pythonhosted.org/packages/d1/4e/e1ce8029fc438086a946739bf9d596f70ff470aad4a8345555920618cabe/pydantic_core-2.46.2-cp312-cp312-win_arm64.whl", hash = "sha256:83aef30f106edcc21a6a4cc44b82d3169a1dbe255508db788e778f3c804d3583", size = 2026188, upload-time = "2026-04-17T09:13:11.083Z" },
{ url = "https://files.pythonhosted.org/packages/07/2b/662e48254479a2d3450ba24b1e25061108b64339794232f503990c519144/pydantic_core-2.46.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:d26e9eea3715008a09a74585fe9becd0c67fbb145dc4df9756d597d7230a652c", size = 2101762, upload-time = "2026-04-17T09:10:13.87Z" },
{ url = "https://files.pythonhosted.org/packages/73/ab/bafd7c7503757ccc8ec4d1911e106fe474c629443648c51a88f08b0fe91a/pydantic_core-2.46.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:48b36e3235140510dc7861f0cd58b714b1cdd3d48f75e10ce52e69866b746f10", size = 1951814, upload-time = "2026-04-17T09:12:25.934Z" },
{ url = "https://files.pythonhosted.org/packages/92/cc/7549c2d57ba2e9a42caa5861a2d398dbe31c02c6aca783253ace59ce84f8/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36b1f99dc451f1a3981f236151465bcf995bbe712d0727c9f7b236fe228a8133", size = 1977329, upload-time = "2026-04-17T09:13:37.605Z" },
{ url = "https://files.pythonhosted.org/packages/18/50/7ed4a8a0d478a4dca8f0134a5efa7193f03cc8520dd4c9509339fb2e5002/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8641c8d535c2d95b45c2e19b646ecd23ebba35d461e0ae48a3498277006250ab", size = 2051832, upload-time = "2026-04-17T09:12:49.771Z" },
{ url = "https://files.pythonhosted.org/packages/dc/16/bb35b193741c0298ddc5f5e4234269efdc0c65e2bcd198aa0de9b68845e4/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20fb194788a0a50993e87013e693494ba183a2af5b44e99cf060bbae10912b11", size = 2233127, upload-time = "2026-04-17T09:11:04.449Z" },
{ url = "https://files.pythonhosted.org/packages/91/a5/98f4b637149185addea19e1785ea20c373cca31b202f589111d8209d9873/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9262d11d0cd11ee3303a95156939402bed6cedfe5ed0e331b95a283a4da6eb8b", size = 2297418, upload-time = "2026-04-17T09:11:25.929Z" },
{ url = "https://files.pythonhosted.org/packages/36/90/93a5d21990b152da7b7507b7fddb0b935f6a0984d57ac3ec45a6e17777a2/pydantic_core-2.46.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac204542736aa295fa25f713b7fad6fc50b46ab7764d16087575c85f085174f3", size = 2093735, upload-time = "2026-04-17T09:12:06.908Z" },
{ url = "https://files.pythonhosted.org/packages/14/22/b8b1ffdddf08b4e84380bcb67f41dbbf4c171377c1d36fc6290794bb2094/pydantic_core-2.46.2-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9a7c43a0584742dface3ca0daf6f719d46c1ac2f87cf080050f9ae052c75e1b2", size = 2127570, upload-time = "2026-04-17T09:11:53.906Z" },
{ url = "https://files.pythonhosted.org/packages/c6/26/e60d72b4e2d0ce1fa811044a974412ac1c567fe067d97b3e6b290530786e/pydantic_core-2.46.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fd05e1edb6a90ad446fa268ab09e59202766b837597b714b2492db11ee87fab9", size = 2183524, upload-time = "2026-04-17T09:11:30.092Z" },
{ url = "https://files.pythonhosted.org/packages/35/32/36bec7584a1eefb17dec4dfa1c946d3fe4440f466c5705b8adfda69c9a9f/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:91155b110788b5501abc7ea954f1d08606219e4e28e3c73a94124307c06efb80", size = 2185408, upload-time = "2026-04-17T09:10:57.228Z" },
{ url = "https://files.pythonhosted.org/packages/fc/d6/1a5689d873620efd67d6b163db0c444c056adb0849b5bc33e2b9f09665a6/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:e4e2c72a529fa03ff228be1d2b76944013f428220b764e03cc50ada67e17a42c", size = 2335171, upload-time = "2026-04-17T09:11:43.369Z" },
{ url = "https://files.pythonhosted.org/packages/3e/8e/675104802abe8ef502b072050ee5f2e915251aa1a3af87e1015ce31ec42d/pydantic_core-2.46.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:56291ec1a11c3499890c99a8fd9053b47e60fe837a77ec72c0671b1b8b3dce24", size = 2362743, upload-time = "2026-04-17T09:10:18.333Z" },
{ url = "https://files.pythonhosted.org/packages/8d/bc/86c5dde4fa6e24467680eef5047da3c1a19be0a527d0d8e14aa76b39307c/pydantic_core-2.46.2-cp313-cp313-win32.whl", hash = "sha256:b50f9c5f826ddca1246f055148df939f5f3f2d0d96db73de28e2233f22210d4c", size = 1958074, upload-time = "2026-04-17T09:12:38.622Z" },
{ url = "https://files.pythonhosted.org/packages/2a/97/2537e8c1282b2c4eb062580c0d7a4339e10b072b803d1ee0b7f1f0a5c22c/pydantic_core-2.46.2-cp313-cp313-win_amd64.whl", hash = "sha256:251a57788823230ca8cbc99e6245d1a2ed6e180ec4864f251c94182c580c7f2e", size = 2071741, upload-time = "2026-04-17T09:13:32.405Z" },
{ url = "https://files.pythonhosted.org/packages/da/aa/2ee75798706f9dbc4e76dbe59e41a396c5c311e3d6223b9cf6a5fa7780be/pydantic_core-2.46.2-cp313-cp313-win_arm64.whl", hash = "sha256:315d32d1a71494d6b4e1e14a9fa7a4329597b4c4340088ad7e1a9dafbeed92a9", size = 2025955, upload-time = "2026-04-17T09:10:15.567Z" },
{ url = "https://files.pythonhosted.org/packages/d0/96/a50ccb6b539ae780f73cea74905468777680e30c6c3bdf714b9d4c116ea0/pydantic_core-2.46.2-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:4f59b45f3ef8650c0c736a57f59031d47ed9df4c0a64e83796849d7d14863a2d", size = 2097111, upload-time = "2026-04-17T09:10:49.617Z" },
{ url = "https://files.pythonhosted.org/packages/34/5f/fdead7b3afa822ab6e5a18ee0ecffd54937de1877c01ed13a342e0fb3f07/pydantic_core-2.46.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a075a29ebef752784a91532a1a85be6b234ccffec0a9d7978a92696387c3da6", size = 1951904, upload-time = "2026-04-17T09:12:32.062Z" },
{ url = "https://files.pythonhosted.org/packages/95/e0/1c5d547e550cdab1bec737492aa08865337af6fe7fc9b96f7f45f17d9519/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d12d786e30c04a9d307c5d7080bf720d9bac7f1668191d8e37633a9562749e2", size = 1978667, upload-time = "2026-04-17T09:11:35.589Z" },
{ url = "https://files.pythonhosted.org/packages/0e/cb/665ce629e218c8228302cb94beff4f6531082a2c87d3ecc3d5e63a26f392/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0d5e6d6343b0b5dcacb3503b5de90022968da8ed0ab9ab39d3eda71c20cbf84e", size = 2046721, upload-time = "2026-04-17T09:11:47.725Z" },
{ url = "https://files.pythonhosted.org/packages/77/e9/6cb2cf60f54c1472bbdfce19d957553b43dbba79d1d7b2930a195c594785/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:233eebac0999b6b9ba76eb56f3ec8fce13164aa16b6d2225a36a79e0f95b5973", size = 2228483, upload-time = "2026-04-17T09:12:08.837Z" },
{ url = "https://files.pythonhosted.org/packages/0d/2a/93e018dd5571f781ebaeda8c0cf65398489d5bee9b1f484df0b6149b43b9/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cc0eee720dd2f14f3b7c349469402b99ad81a174ab49d3533974529e9d93992", size = 2294663, upload-time = "2026-04-17T09:12:52.053Z" },
{ url = "https://files.pythonhosted.org/packages/5e/4f/49e57ca55c770c93d9bb046666a54949b42e3c9099a0c5fe94557873fe30/pydantic_core-2.46.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83ee76bf2c9910513dbc19e7d82367131fa7508dedd6186a462393071cc11059", size = 2098742, upload-time = "2026-04-17T09:13:45.472Z" },
{ url = "https://files.pythonhosted.org/packages/c6/b0/6e46b5cd3332af665f794b8cdeea206618a8630bd9e7bcc36864518fce81/pydantic_core-2.46.2-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:d61db38eb4ee5192f0c261b7f2d38e420b554df8912245e3546aee5c45e2fd78", size = 2125922, upload-time = "2026-04-17T09:12:54.304Z" },
{ url = "https://files.pythonhosted.org/packages/06/d1/40850c81585be443a2abfdf7f795f8fae831baf8e2f9b2133c8246ac671c/pydantic_core-2.46.2-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8f09a713d17bcd55da8ab02ebd9110c5246a49c44182af213b5212800af8bc83", size = 2183000, upload-time = "2026-04-17T09:10:59.027Z" },
{ url = "https://files.pythonhosted.org/packages/04/af/8493d7dfa03ebb7866909e577c6aa65ea0de7377b86023cc51d0c8e11db3/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:30cacc5fb696e64b8ef6fd31d9549d394dd7d52760db072eecb98e37e3af1677", size = 2180335, upload-time = "2026-04-17T09:12:57.01Z" },
{ url = "https://files.pythonhosted.org/packages/72/5b/1f6a344c4ffdf284da41c6067b82d5ebcbd11ce1b515ae4b662d4adb6f61/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:7ccfb105fcfe91a22bbb5563ad3dc124bc1aa75bfd2e53a780ab05f78cdf6108", size = 2330002, upload-time = "2026-04-17T09:12:02.958Z" },
{ url = "https://files.pythonhosted.org/packages/25/ff/9a694126c12d6d2f48a0cafa6f8eef88ef0d8825600e18d03ff2e896c3b2/pydantic_core-2.46.2-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:13ffef637dc8370c249e5b26bd18e9a80a4fca3d809618c44e18ec834a7ca7a8", size = 2359920, upload-time = "2026-04-17T09:10:27.764Z" },
{ url = "https://files.pythonhosted.org/packages/51/c8/3a35c763d68a9cb2675eb10ef242cf66c5d4701b28ae12e688d67d2c180e/pydantic_core-2.46.2-cp314-cp314-win32.whl", hash = "sha256:1b0ab6d756ca2704a938e6c31b53f290c2f9c10d3914235410302a149de1a83e", size = 1953701, upload-time = "2026-04-17T09:13:30.021Z" },
{ url = "https://files.pythonhosted.org/packages/1a/6a/f2726a780365f7dfd89d62036f984f7acb99978c60c5e1fa7c0cb898ed11/pydantic_core-2.46.2-cp314-cp314-win_amd64.whl", hash = "sha256:99ebade8c9ada4df975372d8dd25883daa0e379a05f1cd0c99aa0c04368d01a6", size = 2071867, upload-time = "2026-04-17T09:10:39.205Z" },
{ url = "https://files.pythonhosted.org/packages/e1/79/76baacb9feba3d7c399b245ca1a29c74ea0db04ea693811374827eec2290/pydantic_core-2.46.2-cp314-cp314-win_arm64.whl", hash = "sha256:de87422197cf7f83db91d89c86a21660d749b3cd76cd8a45d115b8e675670f02", size = 2017252, upload-time = "2026-04-17T09:10:26.175Z" },
{ url = "https://files.pythonhosted.org/packages/f1/3b/77c26938f817668d9ad9bab1a905cb23f11d9a3d4bf724d429b3e55a8eaf/pydantic_core-2.46.2-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:236f22b4a206b5b61db955396b7cf9e2e1ff77f372efe9570128ccfcd6a525eb", size = 2094545, upload-time = "2026-04-17T09:12:19.339Z" },
{ url = "https://files.pythonhosted.org/packages/fe/de/42c13f590e3c260966aa49bcdb1674774f975467c49abd51191e502bea28/pydantic_core-2.46.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c2012f64d2cd7cca50f49f22445aa5a88691ac2b4498ee0a9a977f8ca4f7289f", size = 1933953, upload-time = "2026-04-17T09:09:55.889Z" },
{ url = "https://files.pythonhosted.org/packages/4e/84/ebe3ebb3e2d8db656937cfa6f97f544cb7132f2307a4a7dfdcd0ea102a12/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d07d6c63106d3a9c9a333e2636f9c82c703b1a9e3b079299e58747964e4fdb72", size = 1974435, upload-time = "2026-04-17T09:10:12.371Z" },
{ url = "https://files.pythonhosted.org/packages/b9/15/0bf51ca6709477cd4ef86148b6d7844f3308f029eac361dd0383f1e17b1a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c326a2b4b85e959d9a1fc3a11f32f84611b6ec07c053e1828a860edf8d068208", size = 2031113, upload-time = "2026-04-17T09:10:00.752Z" },
{ url = "https://files.pythonhosted.org/packages/02/ae/b7b5af9b79db036d9e61a44c481c17a213dc8fc4b8b71fe6875a72fc778b/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac8a65e798f2462552c00d2e013d532c94d646729dda98458beaf51f9ec7b120", size = 2236325, upload-time = "2026-04-17T09:10:33.227Z" },
{ url = "https://files.pythonhosted.org/packages/a6/ae/ecef7477b5a03d4a499708f7e75d2836452ebb70b776c2d64612b334f57a/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a3c2bc1cc8164bedbc160b7bb1e8cc1e8b9c27f69ae4f9ae2b976cdae02b2dd", size = 2278135, upload-time = "2026-04-17T09:10:23.287Z" },
{ url = "https://files.pythonhosted.org/packages/db/e4/2f9d82faa47af6c39fc3f120145fd915971e1e0cb6b55b494fad9fdf8275/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e69aa5e10b7e8b1bb4a6888650fd12fcbf11d396ca11d4a44de1450875702830", size = 2109071, upload-time = "2026-04-17T09:11:06.149Z" },
{ url = "https://files.pythonhosted.org/packages/f1/9c/677cf10873fbd0b116575ab7b97c90482b21564f8a8040beb18edef7a577/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4e6df5c3301e65fb42bc5338bf9a1027a02b0a31dc7f54c33775229af474daf0", size = 2106028, upload-time = "2026-04-17T09:10:51.525Z" },
{ url = "https://files.pythonhosted.org/packages/d6/53/6a06183544daba51c059123a2064a99039df25f115a06bdb26f2ea177038/pydantic_core-2.46.2-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2c2f6e32548ac8d559b47944effcf8ae4d81c161f6b6c885edc53bc08b8f192d", size = 2164816, upload-time = "2026-04-17T09:11:56.187Z" },
{ url = "https://files.pythonhosted.org/packages/57/6f/10fcdd9e3eca66fc828eef0f6f5850f2dd3bca2c59e6e041fb8bc3da39be/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:b089a81c58e6ea0485562bbbbbca4f65c0549521606d5ef27fba217aac9b665a", size = 2166130, upload-time = "2026-04-17T09:10:03.804Z" },
{ url = "https://files.pythonhosted.org/packages/29/83/92d3fd0e0156cad2e3cb5c26de73794af78ac9fa0c22ab666e566dd67061/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:7f700a6d6f64112ae9193709b84303bbab84424ad4b47d0253301aabce9dfc70", size = 2316605, upload-time = "2026-04-17T09:12:45.249Z" },
{ url = "https://files.pythonhosted.org/packages/97/f1/facffdb970981068219582e499b8d0871ed163ffcc6b347de5c412669e4c/pydantic_core-2.46.2-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:67db6814beaa5fefe91101ec7eb9efda613795767be96f7cf58b1ca8c9ca9972", size = 2358385, upload-time = "2026-04-17T09:09:54.657Z" },
{ url = "https://files.pythonhosted.org/packages/8b/a1/b8160b2f22b2199467bc68581a4ed380643c16b348a27d6165c6c242d694/pydantic_core-2.46.2-cp314-cp314t-win32.whl", hash = "sha256:32fbc7447be8e3be99bf7869f7066308f16be55b61f9882c2cefc7931f5c7664", size = 1942373, upload-time = "2026-04-17T09:12:59.594Z" },
{ url = "https://files.pythonhosted.org/packages/0d/90/db89acabe5b150e11d1b59fe3d947dda2ef6abbfef5c82f056ff63802f5d/pydantic_core-2.46.2-cp314-cp314t-win_amd64.whl", hash = "sha256:b317a2b97019c0b95ce99f4f901ae383f40132da6706cdf1731066a73394c25c", size = 2052078, upload-time = "2026-04-17T09:10:19.96Z" },
{ url = "https://files.pythonhosted.org/packages/97/32/e19b83ceb07a3f1bb21798407790bbc9a31740158fd132b94139cb84e16c/pydantic_core-2.46.2-cp314-cp314t-win_arm64.whl", hash = "sha256:7dcb9d40930dfad7ab6b20bcc6ca9d2b030b0f347a0cd9909b54bd53ead521b1", size = 2016941, upload-time = "2026-04-17T09:12:34.447Z" },
{ url = "https://files.pythonhosted.org/packages/f3/d2/66c146f421178641bda880b0267c0d57dd84f5fec9ecc8e46be17b480742/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e9fcabd1857492b5bf16f90258babde50f618f55d046b1309972da2396321ff9", size = 2091621, upload-time = "2026-04-17T09:12:47.501Z" },
{ url = "https://files.pythonhosted.org/packages/ee/b2/c28419aa9fc8055f4ac8e801d1d11c6357351bfa4321ed9bafab3eb98087/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:fb3ec2c7f54c07b30d89983ce78dc32c37dd06a972448b8716d609493802d628", size = 1937059, upload-time = "2026-04-17T09:10:53.554Z" },
{ url = "https://files.pythonhosted.org/packages/30/ce/cd0824a2db213dc17113291b7a09b9b0ccd9fbf97daa4b81548703341baf/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130a6c837d819ef33e8c2bf702ed2c3429237ea69807f1140943d6f4bdaf52fa", size = 1997278, upload-time = "2026-04-17T09:12:23.784Z" },
{ url = "https://files.pythonhosted.org/packages/c9/69/47283fe3c0c967d3e9e9cd6c42b70907610c8a6f8d6e8381f1bb55f8006c/pydantic_core-2.46.2-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2e25417cec5cd9bddb151e33cb08c50160f317479ecc02b22a95ec18f8fe004", size = 2147096, upload-time = "2026-04-17T09:12:43.124Z" },
]
[[package]]
name = "pyjwt"
version = "2.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" },
]
[[package]]
name = "repl-nix-workspace"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "aiosqlite" },
{ name = "fastapi" },
{ name = "httpx" },
{ name = "passlib", extra = ["bcrypt"] },
{ name = "pydantic", extra = ["email"] },
{ name = "pyjwt" },
{ name = "sqlalchemy" },
{ name = "uvicorn" },
]
[package.metadata]
requires-dist = [
{ name = "aiosqlite", specifier = ">=0.22.1" },
{ name = "fastapi", specifier = ">=0.136.0" },
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "passlib", extras = ["bcrypt"], specifier = ">=1.7.4" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.13.2" },
{ name = "pyjwt", specifier = ">=2.12.1" },
{ name = "sqlalchemy", specifier = ">=2.0.49" },
{ name = "uvicorn", specifier = ">=0.44.0" },
]
[[package]]
name = "sqlalchemy"
version = "2.0.49"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/09/45/461788f35e0364a8da7bda51a1fe1b09762d0c32f12f63727998d85a873b/sqlalchemy-2.0.49.tar.gz", hash = "sha256:d15950a57a210e36dd4cec1aac22787e2a4d57ba9318233e2ef8b2daf9ff2d5f", size = 9898221, upload-time = "2026-04-03T16:38:11.704Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/b3/2de412451330756aaaa72d27131db6dde23995efe62c941184e15242a5fa/sqlalchemy-2.0.49-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbccb45260e4ff1b7db0be80a9025bb1e6698bdb808b83fff0000f7a90b2c0b", size = 2157681, upload-time = "2026-04-03T16:53:07.132Z" },
{ url = "https://files.pythonhosted.org/packages/50/84/b2a56e2105bd11ebf9f0b93abddd748e1a78d592819099359aa98134a8bf/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb37f15714ec2652d574f021d479e78cd4eb9d04396dca36568fdfffb3487982", size = 3338976, upload-time = "2026-04-03T17:07:40Z" },
{ url = "https://files.pythonhosted.org/packages/2c/fa/65fcae2ed62f84ab72cf89536c7c3217a156e71a2c111b1305ab6f0690e2/sqlalchemy-2.0.49-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb9ec6436a820a4c006aad1ac351f12de2f2dbdaad171692ee457a02429b672", size = 3351937, upload-time = "2026-04-03T17:12:23.374Z" },
{ url = "https://files.pythonhosted.org/packages/f8/2f/6fd118563572a7fe475925742eb6b3443b2250e346a0cc27d8d408e73773/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8d6efc136f44a7e8bc8088507eaabbb8c2b55b3dbb63fe102c690da0ddebe55e", size = 3281646, upload-time = "2026-04-03T17:07:41.949Z" },
{ url = "https://files.pythonhosted.org/packages/c5/d7/410f4a007c65275b9cf82354adb4bb8ba587b176d0a6ee99caa16fe638f8/sqlalchemy-2.0.49-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e06e617e3d4fd9e51d385dfe45b077a41e9d1b033a7702551e3278ac597dc750", size = 3316695, upload-time = "2026-04-03T17:12:25.642Z" },
{ url = "https://files.pythonhosted.org/packages/d9/95/81f594aa60ded13273a844539041ccf1e66c5a7bed0a8e27810a3b52d522/sqlalchemy-2.0.49-cp312-cp312-win32.whl", hash = "sha256:83101a6930332b87653886c01d1ee7e294b1fe46a07dd9a2d2b4f91bcc88eec0", size = 2117483, upload-time = "2026-04-03T17:05:40.896Z" },
{ url = "https://files.pythonhosted.org/packages/47/9e/fd90114059175cac64e4fafa9bf3ac20584384d66de40793ae2e2f26f3bb/sqlalchemy-2.0.49-cp312-cp312-win_amd64.whl", hash = "sha256:618a308215b6cececb6240b9abde545e3acdabac7ae3e1d4e666896bf5ba44b4", size = 2144494, upload-time = "2026-04-03T17:05:42.282Z" },
{ url = "https://files.pythonhosted.org/packages/ae/81/81755f50eb2478eaf2049728491d4ea4f416c1eb013338682173259efa09/sqlalchemy-2.0.49-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df2d441bacf97022e81ad047e1597552eb3f83ca8a8f1a1fdd43cd7fe3898120", size = 2154547, upload-time = "2026-04-03T16:53:08.64Z" },
{ url = "https://files.pythonhosted.org/packages/a2/bc/3494270da80811d08bcfa247404292428c4fe16294932bce5593f215cad9/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8e20e511dc15265fb433571391ba313e10dd8ea7e509d51686a51313b4ac01a2", size = 3280782, upload-time = "2026-04-03T17:07:43.508Z" },
{ url = "https://files.pythonhosted.org/packages/cd/f5/038741f5e747a5f6ea3e72487211579d8cbea5eb9827a9cbd61d0108c4bd/sqlalchemy-2.0.49-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47604cb2159f8bbd5a1ab48a714557156320f20871ee64d550d8bf2683d980d3", size = 3297156, upload-time = "2026-04-03T17:12:27.697Z" },
{ url = "https://files.pythonhosted.org/packages/88/50/a6af0ff9dc954b43a65ca9b5367334e45d99684c90a3d3413fc19a02d43c/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:22d8798819f86720bc646ab015baff5ea4c971d68121cb36e2ebc2ee43ead2b7", size = 3228832, upload-time = "2026-04-03T17:07:45.38Z" },
{ url = "https://files.pythonhosted.org/packages/bc/d1/5f6bdad8de0bf546fc74370939621396515e0cdb9067402d6ba1b8afbe9a/sqlalchemy-2.0.49-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9b1c058c171b739e7c330760044803099c7fff11511e3ab3573e5327116a9c33", size = 3267000, upload-time = "2026-04-03T17:12:29.657Z" },
{ url = "https://files.pythonhosted.org/packages/f7/30/ad62227b4a9819a5e1c6abff77c0f614fa7c9326e5a3bdbee90f7139382b/sqlalchemy-2.0.49-cp313-cp313-win32.whl", hash = "sha256:a143af2ea6672f2af3f44ed8f9cd020e9cc34c56f0e8db12019d5d9ecf41cb3b", size = 2115641, upload-time = "2026-04-03T17:05:43.989Z" },
{ url = "https://files.pythonhosted.org/packages/17/3a/7215b1b7d6d49dc9a87211be44562077f5f04f9bb5a59552c1c8e2d98173/sqlalchemy-2.0.49-cp313-cp313-win_amd64.whl", hash = "sha256:12b04d1db2663b421fe072d638a138460a51d5a862403295671c4f3987fb9148", size = 2141498, upload-time = "2026-04-03T17:05:45.7Z" },
{ url = "https://files.pythonhosted.org/packages/28/4b/52a0cb2687a9cd1648252bb257be5a1ba2c2ded20ba695c65756a55a15a4/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24bd94bb301ec672d8f0623eba9226cc90d775d25a0c92b5f8e4965d7f3a1518", size = 3560807, upload-time = "2026-04-03T16:58:31.666Z" },
{ url = "https://files.pythonhosted.org/packages/8c/d8/fda95459204877eed0458550d6c7c64c98cc50c2d8d618026737de9ed41a/sqlalchemy-2.0.49-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a51d3db74ba489266ef55c7a4534eb0b8db9a326553df481c11e5d7660c8364d", size = 3527481, upload-time = "2026-04-03T17:06:00.155Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0a/2aac8b78ac6487240cf7afef8f203ca783e8796002dc0cf65c4ee99ff8bb/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:55250fe61d6ebfd6934a272ee16ef1244e0f16b7af6cd18ab5b1fc9f08631db0", size = 3468565, upload-time = "2026-04-03T16:58:33.414Z" },
{ url = "https://files.pythonhosted.org/packages/a5/3d/ce71cfa82c50a373fd2148b3c870be05027155ce791dc9a5dcf439790b8b/sqlalchemy-2.0.49-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:46796877b47034b559a593d7e4b549aba151dae73f9e78212a3478161c12ab08", size = 3477769, upload-time = "2026-04-03T17:06:02.787Z" },
{ url = "https://files.pythonhosted.org/packages/d5/e8/0a9f5c1f7c6f9ca480319bf57c2d7423f08d31445974167a27d14483c948/sqlalchemy-2.0.49-cp313-cp313t-win32.whl", hash = "sha256:9c4969a86e41454f2858256c39bdfb966a20961e9b58bf8749b65abf447e9a8d", size = 2143319, upload-time = "2026-04-03T17:02:04.328Z" },
{ url = "https://files.pythonhosted.org/packages/0e/51/fb5240729fbec73006e137c4f7a7918ffd583ab08921e6ff81a999d6517a/sqlalchemy-2.0.49-cp313-cp313t-win_amd64.whl", hash = "sha256:b9870d15ef00e4d0559ae10ee5bc71b654d1f20076dbe8bc7ed19b4c0625ceba", size = 2175104, upload-time = "2026-04-03T17:02:05.989Z" },
{ url = "https://files.pythonhosted.org/packages/55/33/bf28f618c0a9597d14e0b9ee7d1e0622faff738d44fe986ee287cdf1b8d0/sqlalchemy-2.0.49-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:233088b4b99ebcbc5258c755a097aa52fbf90727a03a5a80781c4b9c54347a2e", size = 2156356, upload-time = "2026-04-03T16:53:09.914Z" },
{ url = "https://files.pythonhosted.org/packages/d1/a7/5f476227576cb8644650eff68cc35fa837d3802b997465c96b8340ced1e2/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57ca426a48eb2c682dae8204cd89ea8ab7031e2675120a47924fabc7caacbc2a", size = 3276486, upload-time = "2026-04-03T17:07:46.9Z" },
{ url = "https://files.pythonhosted.org/packages/2e/84/efc7c0bf3a1c5eef81d397f6fddac855becdbb11cb38ff957888603014a7/sqlalchemy-2.0.49-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:685e93e9c8f399b0c96a624799820176312f5ceef958c0f88215af4013d29066", size = 3281479, upload-time = "2026-04-03T17:12:32.226Z" },
{ url = "https://files.pythonhosted.org/packages/91/68/bb406fa4257099c67bd75f3f2261b129c63204b9155de0d450b37f004698/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e0400fa22f79acc334d9a6b185dc00a44a8e6578aa7e12d0ddcd8434152b187", size = 3226269, upload-time = "2026-04-03T17:07:48.678Z" },
{ url = "https://files.pythonhosted.org/packages/67/84/acb56c00cca9f251f437cb49e718e14f7687505749ea9255d7bd8158a6df/sqlalchemy-2.0.49-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a05977bffe9bffd2229f477fa75eabe3192b1b05f408961d1bebff8d1cd4d401", size = 3248260, upload-time = "2026-04-03T17:12:34.381Z" },
{ url = "https://files.pythonhosted.org/packages/56/19/6a20ea25606d1efd7bd1862149bb2a22d1451c3f851d23d887969201633f/sqlalchemy-2.0.49-cp314-cp314-win32.whl", hash = "sha256:0f2fa354ba106eafff2c14b0cc51f22801d1e8b2e4149342023bd6f0955de5f5", size = 2118463, upload-time = "2026-04-03T17:05:47.093Z" },
{ url = "https://files.pythonhosted.org/packages/cf/4f/8297e4ed88e80baa1f5aa3c484a0ee29ef3c69c7582f206c916973b75057/sqlalchemy-2.0.49-cp314-cp314-win_amd64.whl", hash = "sha256:77641d299179c37b89cf2343ca9972c88bb6eef0d5fc504a2f86afd15cd5adf5", size = 2144204, upload-time = "2026-04-03T17:05:48.694Z" },
{ url = "https://files.pythonhosted.org/packages/1f/33/95e7216df810c706e0cd3655a778604bbd319ed4f43333127d465a46862d/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c1dc3368794d522f43914e03312202523cc89692f5389c32bea0233924f8d977", size = 3565474, upload-time = "2026-04-03T16:58:35.128Z" },
{ url = "https://files.pythonhosted.org/packages/0c/a4/ed7b18d8ccf7f954a83af6bb73866f5bc6f5636f44c7731fbb741f72cc4f/sqlalchemy-2.0.49-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c821c47ecfe05cc32140dcf8dc6fd5d21971c86dbd56eabfe5ba07a64910c01", size = 3530567, upload-time = "2026-04-03T17:06:04.587Z" },
{ url = "https://files.pythonhosted.org/packages/73/a3/20faa869c7e21a827c4a2a42b41353a54b0f9f5e96df5087629c306df71e/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9c04bff9a5335eb95c6ecf1c117576a0aa560def274876fd156cfe5510fccc61", size = 3474282, upload-time = "2026-04-03T16:58:37.131Z" },
{ url = "https://files.pythonhosted.org/packages/b7/50/276b9a007aa0764304ad467eceb70b04822dc32092492ee5f322d559a4dc/sqlalchemy-2.0.49-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7f605a456948c35260e7b2a39f8952a26f077fd25653c37740ed186b90aaa68a", size = 3480406, upload-time = "2026-04-03T17:06:07.176Z" },
{ url = "https://files.pythonhosted.org/packages/e5/c3/c80fcdb41905a2df650c2a3e0337198b6848876e63d66fe9188ef9003d24/sqlalchemy-2.0.49-cp314-cp314t-win32.whl", hash = "sha256:6270d717b11c5476b0cbb21eedc8d4dbb7d1a956fd6c15a23e96f197a6193158", size = 2149151, upload-time = "2026-04-03T17:02:07.281Z" },
{ url = "https://files.pythonhosted.org/packages/05/52/9f1a62feab6ed368aff068524ff414f26a6daebc7361861035ae00b05530/sqlalchemy-2.0.49-cp314-cp314t-win_amd64.whl", hash = "sha256:275424295f4256fd301744b8f335cff367825d270f155d522b30c7bf49903ee7", size = 2184178, upload-time = "2026-04-03T17:02:08.623Z" },
{ url = "https://files.pythonhosted.org/packages/e5/30/8519fdde58a7bdf155b714359791ad1dc018b47d60269d5d160d311fdc36/sqlalchemy-2.0.49-py3-none-any.whl", hash = "sha256:ec44cfa7ef1a728e88ad41674de50f6db8cfdb3e2af84af86e0041aaf02d43d0", size = 1942158, upload-time = "2026-04-03T16:53:44.135Z" },
]
[[package]]
name = "starlette"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]
[[package]]
name = "uvicorn"
version = "0.44.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/5e/da/6eee1ff8b6cbeed47eeb5229749168e81eb4b7b999a1a15a7176e51410c9/uvicorn-0.44.0.tar.gz", hash = "sha256:6c942071b68f07e178264b9152f1f16dfac5da85880c4ce06366a96d70d4f31e", size = 86947, upload-time = "2026-04-06T09:23:22.826Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/23/a5bbd9600dd607411fa644c06ff4951bec3a4d82c4b852374024359c19c0/uvicorn-0.44.0-py3-none-any.whl", hash = "sha256:ce937c99a2cc70279556967274414c087888e8cec9f9c94644dfca11bd3ced89", size = 69425, upload-time = "2026-04-06T09:23:21.524Z" },
]