mishrabp commited on
Commit
0fb9fce
·
verified ·
1 Parent(s): e11e73d

Upload folder using huggingface_hub

Browse files
Files changed (39) hide show
  1. README.md +28 -64
  2. pyproject.toml +12 -14
  3. run.py +23 -203
  4. src/agora/Dockerfile +63 -0
  5. src/agora/README.md +28 -0
  6. src/agora/backend/__init__.py +0 -0
  7. src/agora/backend/aagents/__init__.py +0 -0
  8. src/agora/backend/aagents/fundamental_analyst.py +62 -0
  9. src/agora/backend/aagents/market_analyst.py +49 -0
  10. src/agora/backend/aagents/orchestrator.py +33 -0
  11. src/agora/backend/aagents/risk_manager.py +54 -0
  12. src/agora/backend/aagents/sentiment_analyst.py +43 -0
  13. src/agora/backend/aagents/strategy_advisor.py +67 -0
  14. src/agora/backend/aagents/volatility_analyst.py +41 -0
  15. src/agora/backend/main.py +141 -0
  16. src/agora/backend/requirements.txt +12 -0
  17. src/agora/backend/teams/__init__.py +0 -0
  18. src/agora/backend/teams/team.py +158 -0
  19. src/agora/backend/tools/__init__.py +0 -0
  20. src/agora/backend/tools/market_data.py +454 -0
  21. src/agora/backend/tools/news_data.py +171 -0
  22. src/agora/backend/tools/pl_calculator.py +98 -0
  23. src/agora/backend/utility/__init__.py +0 -0
  24. src/agora/backend/utility/autogen_model_factory.py +112 -0
  25. src/agora/frontend/.gitignore +24 -0
  26. src/agora/frontend/.vscode/extensions.json +3 -0
  27. src/agora/frontend/README.md +5 -0
  28. src/agora/frontend/index.html +13 -0
  29. src/agora/frontend/package-lock.json +1627 -0
  30. src/agora/frontend/package.json +20 -0
  31. src/agora/frontend/public/favicon.svg +5 -0
  32. src/agora/frontend/public/vite.svg +1 -0
  33. src/agora/frontend/src/App.vue +1888 -0
  34. src/agora/frontend/src/assets/vue.svg +1 -0
  35. src/agora/frontend/src/components/HelloWorld.vue +43 -0
  36. src/agora/frontend/src/main.js +5 -0
  37. src/agora/frontend/src/style.css +118 -0
  38. src/agora/frontend/vite.config.js +7 -0
  39. uv.lock +157 -161
README.md CHANGED
@@ -1,64 +1,28 @@
1
- ---
2
- app_port: 7860
3
- colorFrom: blue
4
- colorTo: gray
5
- emoji: 📈
6
- license: mit
7
- pinned: false
8
- sdk: docker
9
- short_description: Multi-agent AI Market Analyst
10
- title: AI Market Analyst v2
11
- ---
12
-
13
- # 📈 AI Market Analyst --- Upgraded Architecture
14
-
15
- This application has been upgraded to a decoupled architecture with a **FastAPI backend** and a **Vue.js frontend**. The multi-agent swarm now communicates via Server-Sent Events (SSE) for real-time analysis streaming.
16
-
17
- ## 🏗️ New Architecture
18
-
19
- ### 📂 `backend/`
20
- - **FastAPI**: Exposes an SSE endpoint for real-time agent streams.
21
- - **Agents**: Same powerful multi-agent team (Market, Sentiment, Strategy, Risk).
22
- - **Tools**: Technical analysis, DuckDuckGo news scraping, and FinBERT sentiment analysis.
23
-
24
- ### 📂 `frontend/`
25
- - **Vue.js 3 + Vite**: A modern, glassy UI built for performance and intuition.
26
- - **Glassmorphism Design**: High-end visuals with backdrop blurs and neon accents.
27
- - **Real-time Streaming**: Watch agents "think" and communicate as it happens.
28
-
29
- ---
30
-
31
- ## 🚀 Getting Started
32
-
33
- ### 1. Run the Backend
34
- ```bash
35
- cd src/market-analyst/backend
36
- # Install dependencies (if not already in root env)
37
- pip install -r requirements.txt
38
- # Launch FastAPI
39
- uvicorn main:app --reload --port 8000
40
- ```
41
- Backend API will be at `http://localhost:8000`
42
-
43
- ### 2. Run the Frontend
44
- ```bash
45
- cd src/market-analyst/frontend
46
- # Install dependencies
47
- npm install
48
- # Launch Vite
49
- npm run dev
50
- ```
51
- Frontend UI will be at `http://localhost:5173`
52
-
53
- ---
54
-
55
- ## ✨ Features
56
- - **SSE Streaming**: Agents stream results character-by-character for a more interactive experience.
57
- - **Glassy Dashboard**: A premium, dark-mode adaptive UI.
58
- - **Multi-Agent Coordination**: MarketAnalyst (Technicals), SentimentAnalyst (News), StrategyAdvisor (Logic), RiskManager (Validation).
59
- - **FinBERT Integration**: Real-time financial sentiment analysis on full article text.
60
-
61
- ---
62
-
63
- ## ⚠️ Financial Disclaimer
64
- **Educational use only.** This tool is not a financial advisor. Do not use for real-money trading without consulting a certified professional.
 
1
+ ---
2
+ title: Agora
3
+ emoji: 🏛️
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: mit
10
+ short_description: Real-time streaming multi-agent market intelligence
11
+ ---
12
+
13
+ # Agora
14
+
15
+ Agora — named after the ancient Greek marketplace — is a real-time multi-agent market intelligence platform. A swarm of four specialist agents analyses a stock simultaneously and streams their findings character-by-character to a modern Vue.js dashboard.
16
+
17
+ ## What it does
18
+
19
+ - **Market Analyst** — technical indicators: SMA, RSI, MACD, Bollinger Bands
20
+ - **Sentiment Analyst** — FinBERT sentiment scoring on live news articles
21
+ - **Strategy Advisor** — synthesises technicals and sentiment into actionable strategy
22
+ - **Risk Manager** — validates the strategy against volatility and risk thresholds
23
+
24
+ ## Stack
25
+
26
+ Vue.js 3 + Vite · FastAPI · Server-Sent Events (SSE) · OpenAI Agents SDK · FinBERT · DuckDuckGo
27
+
28
+ > **Disclaimer:** For informational purposes only. Not financial advice.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
pyproject.toml CHANGED
@@ -23,7 +23,7 @@ dependencies = [
23
  # =======================
24
  "langchain>=1.0.7",
25
  "langchain-community>=0.4.1",
26
- "langgraph>=1.0.3",
27
  "langgraph-checkpoint-sqlite>=3.0.0",
28
  "langsmith>=0.8.0",
29
  "langchain-text-splitters>=1.1.2",
@@ -74,8 +74,8 @@ dependencies = [
74
  # =======================
75
  "psutil>=7.0.0",
76
  "python-dotenv>=1.2.2",
77
- "requests>=2.32.3",
78
- "aiohttp>=3.8.5",
79
  "httpx>=0.28.1",
80
  "speedtest-cli>=2.1.3",
81
  "logfire",
@@ -98,7 +98,7 @@ dependencies = [
98
  # VISUAL / UI / PDF
99
  # =======================
100
  "plotly>=6.5.0",
101
- "streamlit>=1.51.0",
102
  "reportlab>=4.4.5",
103
  "fastapi",
104
  "Pillow",
@@ -110,7 +110,7 @@ dependencies = [
110
  # =======================
111
  # AUDIO / VIDEO
112
  # =======================
113
- "yt_dlp>=2025.11.12",
114
  "openai-whisper==20250625",
115
  "numba==0.63.1",
116
  "llvmlite==0.46.0",
@@ -138,6 +138,7 @@ dependencies = [
138
  # =======================
139
  # OBSERVABILITY
140
  # =======================
 
141
  "openinference-instrumentation-autogen>=0.1.0",
142
  "openinference-instrumentation-openai>=0.1.15",
143
  "opentelemetry-sdk>=1.20.0",
@@ -157,9 +158,10 @@ dependencies = [
157
 
158
  [dependency-groups]
159
  dev = [
160
- "pytest>=8.3.3",
161
  "ipykernel>=7.1.0",
162
  "pytest-asyncio",
 
163
  ]
164
 
165
  # ============================================================
@@ -175,20 +177,16 @@ build-backend = "setuptools.build_meta"
175
  # PACKAGING & DISCOVERY
176
  # ============================================================
177
  # Tells setuptools where to find the source code.
178
- # This makes 'common' and 'src' importable when installed (pip install -e .).
179
  [tool.setuptools.packages.find]
180
- where = ["."] # Look in the project root
181
- include = ["common*", "src*"] # Treat 'common' and 'src' folders as packages
182
 
183
 
184
  # ============================================================
185
  # PYTEST SETTINGS
186
  # ============================================================
187
- # Configures the test runner to automatically find code.
188
  [tool.pytest.ini_options]
189
- # Adds 'src' and 'common' to the python path during tests.
190
- # This allows tests to import modules (e.g., 'import travel_agent')
191
- # just like the apps do locally, preventing ModuleNotFoundError.
192
- pythonpath = ["src", "common"]
193
  testpaths = ["tests"] # Only look for tests in the 'tests' directory
194
  addopts = "-q" # Run in quiet mode (less verbose output)
 
 
23
  # =======================
24
  "langchain>=1.0.7",
25
  "langchain-community>=0.4.1",
26
+ "langgraph>=1.0.10rc1",
27
  "langgraph-checkpoint-sqlite>=3.0.0",
28
  "langsmith>=0.8.0",
29
  "langchain-text-splitters>=1.1.2",
 
74
  # =======================
75
  "psutil>=7.0.0",
76
  "python-dotenv>=1.2.2",
77
+ "requests>=2.33.0",
78
+ "aiohttp>=3.14.0",
79
  "httpx>=0.28.1",
80
  "speedtest-cli>=2.1.3",
81
  "logfire",
 
98
  # VISUAL / UI / PDF
99
  # =======================
100
  "plotly>=6.5.0",
101
+ "streamlit>=1.54.0",
102
  "reportlab>=4.4.5",
103
  "fastapi",
104
  "Pillow",
 
110
  # =======================
111
  # AUDIO / VIDEO
112
  # =======================
113
+ "yt_dlp>=2026.2.21",
114
  "openai-whisper==20250625",
115
  "numba==0.63.1",
116
  "llvmlite==0.46.0",
 
138
  # =======================
139
  # OBSERVABILITY
140
  # =======================
141
+ "langfuse>=3.0.0",
142
  "openinference-instrumentation-autogen>=0.1.0",
143
  "openinference-instrumentation-openai>=0.1.15",
144
  "opentelemetry-sdk>=1.20.0",
 
158
 
159
  [dependency-groups]
160
  dev = [
161
+ "pytest>=9.0.3",
162
  "ipykernel>=7.1.0",
163
  "pytest-asyncio",
164
+ "pytest-cov>=7.1.0",
165
  ]
166
 
167
  # ============================================================
 
177
  # PACKAGING & DISCOVERY
178
  # ============================================================
179
  # Tells setuptools where to find the source code.
 
180
  [tool.setuptools.packages.find]
181
+ where = ["."]
182
+ include = ["src*"]
183
 
184
 
185
  # ============================================================
186
  # PYTEST SETTINGS
187
  # ============================================================
 
188
  [tool.pytest.ini_options]
189
+ pythonpath = ["src"]
 
 
 
190
  testpaths = ["tests"] # Only look for tests in the 'tests' directory
191
  addopts = "-q" # Run in quiet mode (less verbose output)
192
+ asyncio_mode = "auto" # Auto-detect async tests (requires pytest-asyncio)
run.py CHANGED
@@ -29,152 +29,42 @@ load_dotenv(override=True)
29
 
30
  # App registry - maps app names to their paths and entry points
31
  APP_REGISTRY: Dict[str, Dict[str, str]] = {
32
- "healthcare": {
33
- "path": "src/healthcare-assistant",
34
  "entry": "app.py",
35
- "description": "Healthcare Assistant - Medical information with RAG and web search"
36
  },
37
- "deep-research_v1": {
38
- "path": "src/deep-research_v1",
39
  "entry": "app.py",
40
- "description": "Deep Research AI - Comprehensive research assistant"
41
  },
42
- "deep-research_v2": {
43
- "path": "src/deep-research_v2",
44
  "entry": "app.py",
45
- "description": "Deep Research AI - Comprehensive research assistant"
46
  },
47
- "stock-analyst": {
48
- "path": "src/stock-analyst",
49
  "entry": "app.py",
50
- "description": "Stock Analyst - Financial analysis and stock recommendations"
51
  },
52
- "travel-agent": {
53
- "path": "src/travel-agent",
54
  "entry": "app.py",
55
- "description": "Travel Agent - Trip planning and travel recommendations"
56
  },
57
- "trip-planner": {
58
- "path": "src/trip-planner",
59
  "entry": "main.py",
60
  "type": "fastapi",
61
- "description": "Trip Planner - Detailed trip itinerary planning"
62
  },
63
- "chatbot_v1": {
64
- "path": "src/chatbot_v1",
65
- "entry": "app.py",
66
- "description": "General Chatbot - Multi-purpose conversational AI"
67
- },
68
- "chatbot_v2": {
69
- "path": "src/chatbot_v2",
70
- "entry": "app.py",
71
- "description": "Layered Chatbot (ReAct) - Advanced Architecture"
72
- },
73
- "accessibility_v2": {
74
- "path": "src/accessibility_v2",
75
- "entry": "app.py",
76
- "description": "Accessibility Auditor V2 - Layered Architecture"
77
- },
78
- "accessibility_v1": {
79
- "path": "src/accessibility_v1",
80
- "entry": "app.py",
81
- "description": "Accessibility Tools - Assistive technology applications"
82
- },
83
- "literature-review": {
84
- "path": "src/literature-review",
85
- "entry": "app.py",
86
- "description": "Literature Review Assistant - Multi-agent literature review tool"
87
- },
88
- "market-analyst": {
89
- "path": "src/market-analyst",
90
  "entry": "backend/main.py",
91
  "type": "fastapi",
92
- "description": "Market Analyst - Decoupled Multi-agent market analysis (Vue.js + FastAPI)"
93
- },
94
- "image": {
95
- "path": "src/image-generator",
96
- "entry": "app.py",
97
- "description": "Image Generator - Multi-agent image generation tool"
98
- },
99
- "interview-assistant": {
100
- "path": "src/interview-assistant",
101
- "entry": "app.py",
102
- "description": "Interview Assistant - Multi-agent interview tool"
103
- },
104
- "finadvisor-lg": {
105
- "path": "src/finadvisor",
106
- "entry": "app-lg.py",
107
- "description": "Financial Advisor - Multi-agent financial advisor tool using LangGraph"
108
- },
109
- "finadvisor-oai": {
110
- "path": "src/finadvisor",
111
- "entry": "app-oai.py",
112
- "description": "Financial Advisor - Multi-agent financial advisor tool using OpenAI"
113
- },
114
- "finadvisor-phi": {
115
- "path": "src/finadvisor",
116
- "entry": "app-phi.py",
117
- "description": "Financial Advisor - Multi-agent financial advisor tool using Phidata"
118
- }
119
- ,
120
- "finadvisor-ag": {
121
- "path": "src/finadvisor",
122
- "entry": "app-ag.py",
123
- "description": "Financial Advisor - Multi-agent financial advisor tool using Autogen"
124
- },
125
- "mcp-trader": {
126
- "path": "src/mcp-trader",
127
- "entry": "server.py",
128
- "type": "script",
129
- "description": "Strategies MCP Server - FastMCP server for trading strategies"
130
- },
131
- "mcp-web": {
132
- "path": "src/mcp-web",
133
- "entry": "server.py",
134
- "type": "script",
135
- "description": "Web MCP Server - Search, Extract, Wikipedia, Arxiv"
136
- },
137
- "mcp-azure-sre": {
138
- "path": "src/mcp-azure-sre",
139
- "entry": "server.py",
140
- "type": "script",
141
- "description": "Azure SRE MCP Server - Manage Azure Resources & Monitoring"
142
- },
143
- "mcp-rag-secure": {
144
- "path": "src/mcp-rag-secure",
145
- "entry": "server.py",
146
- "type": "script",
147
- "description": "Secure RAG MCP Server - Multi-tenant knowledge base"
148
- },
149
- "mcp-trading-research": {
150
- "path": "src/mcp-trading-research",
151
- "entry": "server.py",
152
- "type": "script",
153
- "description": "Trading Research MCP Server - News, Insider, Analysts"
154
- },
155
- "mcp-github": {
156
- "path": "src/mcp-github",
157
- "entry": "server.py",
158
- "type": "script",
159
- "description": "GitHub MCP Server - Issues, PRs, Alerts"
160
- },
161
- "mcp-seo": {
162
- "path": "src/mcp-seo",
163
- "entry": "server.py",
164
- "type": "script",
165
- "description": "SEO & ADA MCP Server - Website Audits"
166
- },
167
- "github-portal": {
168
- "path": "src/github-portal",
169
- "entry": "app.py",
170
- "type": "streamlit",
171
- "description": "GitHub Portal - Repository health dashboard for issues, security, and pipelines"
172
- },
173
- "mcp-hub": {
174
- "path": "src/mcp-hub",
175
- "entry": "package.json",
176
- "type": "npm",
177
- "description": "MCP HUB - Discovery and monitoring portal (Vue.js)"
178
  },
179
  "test": {
180
  "path": ".",
@@ -182,12 +72,6 @@ APP_REGISTRY: Dict[str, Dict[str, str]] = {
182
  "type": "test",
183
  "description": "Run Project Tests - Executes pytest suite"
184
  },
185
- "salesdata": {
186
- "path": "src/salesdata",
187
- "entry": "app.py",
188
- "type": "salesdata",
189
- "description": "Sales Data Agent - Agentic RAG over sales CSV (FastAPI + Streamlit)"
190
- }
191
  }
192
 
193
 
@@ -272,8 +156,8 @@ def launch_app(app_name: str, port: Optional[int] = None):
272
  env["PYTHONPATH"] = str(project_root) + os.pathsep + env.get("PYTHONPATH", "")
273
 
274
  # Decoupled App Logic: Build frontend if needed
275
- if app_name == "market-analyst":
276
- frontend_dir = project_root / "src/market-analyst/frontend"
277
  dist_dir = frontend_dir / "dist"
278
  if not dist_dir.exists():
279
  print("\n🛠️ Frontend build missing. Building now...")
@@ -334,70 +218,6 @@ def launch_app(app_name: str, port: Optional[int] = None):
334
  # Change to app directory and run
335
  os.chdir(app_dir)
336
 
337
- # Special case for mcp-hub: launch backend API first
338
- if app_name == "mcp-hub":
339
- print("🚀 Starting MCP Hub Backend API on port 8001...")
340
- api_cmd = [python_exe, "api.py"]
341
- subprocess.Popen(api_cmd, env=env, shell=is_windows)
342
-
343
- # Special case for salesdata: launch FastAPI backend + Streamlit frontend
344
- if app_name == "salesdata":
345
- api_port = 8080
346
- ui_port = port if port else 8501
347
-
348
- # Aggressively kill any stale processes on both ports
349
- import platform
350
- if platform.system() != "Windows":
351
- for p in [api_port, ui_port]:
352
- # lsof is more reliable than fuser for finding PIDs by port
353
- result = subprocess.run(
354
- ["lsof", "-ti", f":{p}"],
355
- capture_output=True, text=True
356
- )
357
- pids = result.stdout.strip().split()
358
- for pid in pids:
359
- subprocess.run(["kill", "-9", pid],
360
- stderr=subprocess.DEVNULL, stdout=subprocess.DEVNULL)
361
- time.sleep(1) # let OS release ports
362
- print(f"🧹 Cleaned up ports {api_port} and {ui_port}")
363
-
364
- print(f"🚀 Starting Sales Agent API on port {api_port}...")
365
- api_cmd = [
366
- python_exe, "-m", "uvicorn", "api:app",
367
- "--host", "0.0.0.0", "--port", str(api_port)
368
- ]
369
- subprocess.Popen(api_cmd, env=env, shell=is_windows, cwd=app_dir)
370
-
371
- # Poll /health until agent is ready (instead of a fixed sleep)
372
- import urllib.request, urllib.error, json as _json
373
- health_url = f"http://localhost:{api_port}/health"
374
- print(f"⏳ Waiting for API + agent to be ready", end="", flush=True)
375
- timeout = 90 # seconds
376
- ready = False
377
- for _ in range(timeout):
378
- time.sleep(1)
379
- print(".", end="", flush=True)
380
- try:
381
- with urllib.request.urlopen(health_url, timeout=2) as r:
382
- data = _json.loads(r.read())
383
- if data.get("agent_ready"):
384
- ready = True
385
- break
386
- except Exception:
387
- pass
388
- print()
389
- if not ready:
390
- print("⚠️ API did not become ready within 90s — launching UI anyway")
391
-
392
- print(f"🌐 Starting Streamlit UI on port {ui_port}...")
393
- ui_cmd = [
394
- python_exe, "-m", "streamlit", "run", "app.py",
395
- "--server.port", str(ui_port)
396
- ]
397
- subprocess.run(ui_cmd, env=env, shell=is_windows)
398
- return
399
-
400
-
401
 
402
 
403
  subprocess.run(cmd, env=env, shell=is_windows)
 
29
 
30
  # App registry - maps app names to their paths and entry points
31
  APP_REGISTRY: Dict[str, Dict[str, str]] = {
32
+ "nexus": {
33
+ "path": "src/nexus",
34
  "entry": "app.py",
35
+ "description": "Nexus - AI Research Assistant - Multi-specialist orchestrator for finance, news, and web research"
36
  },
37
+ "athena": {
38
+ "path": "src/athena",
39
  "entry": "app.py",
40
+ "description": "Athena - Deep Research Reporter - Plans, searches, and synthesises comprehensive research reports"
41
  },
42
+ "remedy": {
43
+ "path": "src/remedy",
44
  "entry": "app.py",
45
+ "description": "Remedy - Healthcare RAG Advisor - Medical information retrieval using RAG and web search"
46
  },
47
+ "midas": {
48
+ "path": "src/midas",
49
  "entry": "app.py",
50
+ "description": "Midas - Stock Investment Analyst - Multi-agent investment team for technical and sentiment analysis"
51
  },
52
+ "odyssey": {
53
+ "path": "src/odyssey",
54
  "entry": "app.py",
55
+ "description": "Odyssey - Travel Planner - AI-powered trip planning with flight, hotel, and itinerary recommendations"
56
  },
57
+ "waypoint": {
58
+ "path": "src/waypoint",
59
  "entry": "main.py",
60
  "type": "fastapi",
61
+ "description": "Waypoint - Trip Planner API - Phidata-powered trip itinerary planning REST API"
62
  },
63
+ "agora": {
64
+ "path": "src/agora",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  "entry": "backend/main.py",
66
  "type": "fastapi",
67
+ "description": "Agora - AI Market Analyst - Real-time multi-agent market analysis with streaming (Vue.js + FastAPI)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  },
69
  "test": {
70
  "path": ".",
 
72
  "type": "test",
73
  "description": "Run Project Tests - Executes pytest suite"
74
  },
 
 
 
 
 
 
75
  }
76
 
77
 
 
156
  env["PYTHONPATH"] = str(project_root) + os.pathsep + env.get("PYTHONPATH", "")
157
 
158
  # Decoupled App Logic: Build frontend if needed
159
+ if app_name == "agora":
160
+ frontend_dir = project_root / "src/agora/frontend"
161
  dist_dir = frontend_dir / "dist"
162
  if not dist_dir.exists():
163
  print("\n🛠️ Frontend build missing. Building now...")
 
218
  # Change to app directory and run
219
  os.chdir(app_dir)
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
 
223
  subprocess.run(cmd, env=env, shell=is_windows)
src/agora/Dockerfile ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Stage 1: Build Frontend
2
+ FROM node:20-slim AS frontend-builder
3
+ WORKDIR /app/frontend
4
+ COPY src/market-analyst/frontend/package*.json ./
5
+ RUN npm install
6
+ COPY src/market-analyst/frontend/ ./
7
+ RUN npm run build
8
+
9
+ # Stage 2: Backend & Runtime
10
+ FROM python:3.12-slim
11
+ WORKDIR /app
12
+
13
+ ENV PYTHONUNBUFFERED=1 \
14
+ DEBIAN_FRONTEND=noninteractive \
15
+ PYTHONPATH=/app:$PYTHONPATH
16
+
17
+ # System deps
18
+ RUN apt-get update && apt-get install -y \
19
+ git build-essential curl \
20
+ && rm -rf /var/lib/apt/lists/*
21
+
22
+ # Install uv
23
+ RUN curl -LsSf https://astral.sh/uv/install.sh | sh
24
+ ENV PATH="/root/.local/bin:$PATH"
25
+
26
+ # Copy project metadata
27
+ COPY pyproject.toml .
28
+ COPY uv.lock .
29
+
30
+ # Copy application code
31
+ COPY common/ ./common/
32
+ COPY src/market-analyst/backend/ ./src/market-analyst/backend/
33
+
34
+ # Copy built frontend from Stage 1 to the location FastAPI expects
35
+ COPY --from=frontend-builder /app/frontend/dist ./src/market-analyst/frontend/dist
36
+
37
+ # Install dependencies
38
+ RUN uv sync --frozen --no-dev && \
39
+ uv pip install -e . --system
40
+
41
+ # Create a non-root user for HF Spaces security compliance
42
+ RUN useradd -m -u 1000 user
43
+
44
+ # Set env vars for cache (must be writable by user)
45
+ ENV HF_HOME=/app/hf_cache \
46
+ TRANSFORMERS_CACHE=/app/hf_cache \
47
+ NUMBA_CACHE_DIR=/app/numba_cache
48
+
49
+ # Create cache directories and ownership
50
+ RUN mkdir -p /app/hf_cache /app/numba_cache && \
51
+ chown -R user:user /app
52
+
53
+ # Switch to non-root user
54
+ USER user
55
+
56
+ # Preload FinBERT model (as user, to ensure cache writes to correct location)
57
+ RUN python -c "from transformers import pipeline; pipeline('sentiment-analysis', model='ProsusAI/finbert', device=-1)"
58
+
59
+ # HF Spaces default port is 7860
60
+ EXPOSE 7860
61
+
62
+ # Run uvicorn on port 7860
63
+ CMD ["uvicorn", "src.market-analyst.backend.main:app", "--host", "0.0.0.0", "--port", "7860"]
src/agora/README.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Agora
3
+ emoji: 🏛️
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: mit
10
+ short_description: Real-time streaming multi-agent market intelligence
11
+ ---
12
+
13
+ # Agora
14
+
15
+ Agora — named after the ancient Greek marketplace — is a real-time multi-agent market intelligence platform. A swarm of four specialist agents analyses a stock simultaneously and streams their findings character-by-character to a modern Vue.js dashboard.
16
+
17
+ ## What it does
18
+
19
+ - **Market Analyst** — technical indicators: SMA, RSI, MACD, Bollinger Bands
20
+ - **Sentiment Analyst** — FinBERT sentiment scoring on live news articles
21
+ - **Strategy Advisor** — synthesises technicals and sentiment into actionable strategy
22
+ - **Risk Manager** — validates the strategy against volatility and risk thresholds
23
+
24
+ ## Stack
25
+
26
+ Vue.js 3 + Vite · FastAPI · Server-Sent Events (SSE) · OpenAI Agents SDK · FinBERT · DuckDuckGo
27
+
28
+ > **Disclaimer:** For informational purposes only. Not financial advice.
src/agora/backend/__init__.py ADDED
File without changes
src/agora/backend/aagents/__init__.py ADDED
File without changes
src/agora/backend/aagents/fundamental_analyst.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.market_data import get_fundamental_data, get_market_indices, get_analyst_consensus
4
+
5
+ def get_fundamental_analyst(model_client):
6
+
7
+ fundamental_tool = FunctionTool(get_fundamental_data, description="Fetch fundamental financial metrics like P/E, PEG, Debt/Equity, and Profit Margin.")
8
+ market_tool = FunctionTool(get_market_indices, description="Get Market Context (SPY/VIX).")
9
+ consensus_tool = FunctionTool(get_analyst_consensus, description="Get Analyst Ratings & Targets.")
10
+
11
+ return AssistantAgent(
12
+ name="FundamentalAnalyst",
13
+ model_client=model_client,
14
+ tools=[market_tool, fundamental_tool, consensus_tool],
15
+ system_message="""
16
+ You are a Fundamental Analyst specializing in company valuation and financial health.
17
+
18
+ MANDATORY WORKFLOW:
19
+
20
+ STEP 1: CALL get_market_indices
21
+ STEP 2: CALL get_fundamental_data
22
+ STEP 3: CALL get_analyst_consensus
23
+
24
+ DO NOT proceed without calling ALL THREE tools.
25
+
26
+ CRITICAL: Provide your full analysis in ROUND 1 ONLY. In subsequent rounds, simply say "Fundamentals stable."
27
+
28
+ STEP 4: Evaluate Valuation & Growth (USE 'valuation_score' & 'quality_score')
29
+ - If UNDERVALUED: Bullish Factor.
30
+ - If PREMIUM: Bearish/Neutral Factor (unless High Growth).
31
+ - If HIGH_QUALITY: Bullish Factor.
32
+
33
+ STEP 5: BINARY EVENT RISK (CRITICAL)
34
+ - Check "next_earnings_date" from tool.
35
+ - If Earnings is within 14 days: "CRITICAL BINARY EVENT". Recommend avoiding short-duration short-volatility strategies (like Iron Condors).
36
+ - If Earnings is 14-30 days: "ELEVATED EVENT RISK".
37
+
38
+ STEP 6: Assess Financial Health
39
+ - Debt/Equity Ratio: <0.5 (Safe), >1.0 (Risky).
40
+ - Profit Margin: >20% (Excellent), <10% (Weak).
41
+
42
+ STEP 7: Market & Sector Context
43
+ - Market: If VIX > 25, PENALIZE High Debt/High P/E.
44
+ - Sector Standards: Tech (Higher P/E ok), Utilities (High Debt ok).
45
+
46
+ STEP 8: Analyst Consensus Check
47
+ - Ratings: "buy" or "strong buy" = Positive. "sell" = Negative.
48
+ - Price Target: If Target < Current Price = Downside Risk (Bearish).
49
+ - Upside Potential: >20% is Strong Bullish factor.
50
+
51
+ STEP 9: Assign Fundamental Strength Rating
52
+ - "Strong": Great Valuation + Safe Debt + Analyst Buy Support.
53
+ - "Stable": Fair metrics + Neutral Analysts.
54
+ - "Weak": Overvalued OR High Debt OR Analyst Sell Ratings.
55
+
56
+ STEP 10: Output Structured Summary
57
+ - BE CONCISE: Use maximum 5 bullet points.
58
+ - NO conversational filler.
59
+ - Include: Strength Rating, Binary Event (Earnings), Valuation vs Peers, Health Summary, and Consensus.
60
+ - TERMINATION: End your message with [[DATA_COLLECTION_COMPLETE]] to move to the strategy phase.
61
+ """
62
+ )
src/agora/backend/aagents/market_analyst.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.market_data import get_current_price, get_technical_indicators, get_market_indices
4
+
5
+ def get_technical_analyst(model_client):
6
+
7
+ # Wrap tools
8
+ price_tool = FunctionTool(get_current_price, description="Get current price of a stock.")
9
+ tech_tool = FunctionTool(get_technical_indicators, description="Calculate SMA (20/50/200), EMA (20), RSI (14), and MACD technical indicators.")
10
+ market_tool = FunctionTool(get_market_indices, description="Get Market Context (SPY/VIX).")
11
+
12
+ return AssistantAgent(
13
+ name="TechnicalAnalyst",
14
+ model_client=model_client,
15
+ tools=[market_tool, price_tool, tech_tool],
16
+ system_message="""
17
+ You are a Master Technical Analyst specializing in chart patterns and momentum.
18
+
19
+ MANDATORY WORKFLOW:
20
+
21
+ STEP 1: CALL get_market_indices (Market Context)
22
+ STEP 2: CALL get_current_price (Stock Price)
23
+ STEP 3: CALL get_technical_indicators (Indicators)
24
+
25
+ DO NOT proceed without calling ALL tools first.
26
+
27
+ STEP 4: Analyze Market Context (Top-Down)
28
+ - If SPY is BEARISH, bias is SHORT/HEDGE.
29
+ - If VIX is HIGH (>30), bias is CAUTION.
30
+
31
+ STEP 5: Analyze Trend & Structure (USE 'trend_signal' from tool)
32
+ - If 'trend_signal' is STRONG_BULLISH/BULLISH -> Bullish Bias
33
+ - If 'trend_signal' is STRONG_BEARISH/BEARISH -> Bearish Bias
34
+ - Reference SMA/EMA levels as support/resistance.
35
+
36
+ STEP 6: Momentum Analysis (USE 'rsi_signal' & 'macd_signal')
37
+ - Check RSI status (OVERSOLD/OVERBOUGHT/NEUTRAL).
38
+ - Check MACD Crossover status.
39
+
40
+ STEP 7: Identify Support/Resistance Zones
41
+ - Reference SMA 50/200 as Primary levels.
42
+ - Reference 52-week High/Low as Secondary levels.
43
+
44
+ STEP 8: Output Structured Summary
45
+ - BE CONCISE: Use maximum 5 bullet points.
46
+ - NO conversational filler.
47
+ - Include: Current Price, Trend Signal, RSI/MACD status, and Support/Resistance levels.
48
+ """
49
+ )
src/agora/backend/aagents/orchestrator.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+
3
+ def get_lead_orchestrator(model_client):
4
+ return AssistantAgent(
5
+ name="LeadOrchestrator",
6
+ model_client=model_client,
7
+ system_message="""
8
+ You are the Lead Orchestrator and Central Control. Your role is NOT to call market tools, but to be the "Master Thinker" who ensures a SOLID, self-reflected strategy.
9
+
10
+ MANDATORY OPERATIONAL PROTOCOL:
11
+
12
+ PHASE 1: THE STUDY (GAP ANALYSIS)
13
+ - You speak after all four Analyst agents (Technical, Volatility, Sentiment, Fundamental).
14
+ - You MUST summarize the findings into a "Global Context".
15
+ - SEARCH FOR GAPS: Look for contradictions. (e.g., "Technical is Bullish but Volatility is at a 52-week high for earnings - the StrategyAdvisor needs to address this contradiction.")
16
+ - Identify any "Binary Risks" that were mentioned by Sentiment/Fundamental but might be overlooked.
17
+
18
+ PHASE 2: THE CHALLENGE (CROSS-QUESTIONING)
19
+ - After the StrategyAdvisor proposes a DRAFT, you MUST cross-question it based on Phase 1's findings.
20
+ - Example: "StrategyAdvisor, given the 14-day earnings gap flagged by the FundamentalAnalyst, why did you choose a 30-day vertical spread instead of a diagonal/calendar?"
21
+ - You facilitate the dialogue between StrategyAdvisor and RiskManager.
22
+
23
+ PHASE 3: THE FINAL JUDGMENT
24
+ - You are the ONLY agent who can issue [[ANALYSIS_JUDGMENT_COMPLETE]].
25
+ - WATCH THE RISK MANAGER: If the RiskManager outputs "APPROVED", you MUST immediately respond with: "ORCHESTRATOR_DECISION: FINAL_APPROVAL. [[ANALYSIS_JUDGMENT_COMPLETE]]"
26
+ - NO PLEASANTRIES: Do not say "Thank you", "Great job", or "You're welcome".
27
+ - LOOP BREAKING: If you see the same argument or error twice, issue a "WAIT" decision and terminate.
28
+
29
+ TERMINATION:
30
+ - When satisfied, output: "ORCHESTRATOR_DECISION: FINAL_APPROVAL. [[ANALYSIS_JUDGMENT_COMPLETE]]"
31
+ - If the risk remains too high or agents are looping: "ORCHESTRATOR_DECISION: WAIT. [[ANALYSIS_JUDGMENT_COMPLETE]]"
32
+ """
33
+ )
src/agora/backend/aagents/risk_manager.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.pl_calculator import calculate_strategy_metrics
4
+
5
+ def get_risk_manager(model_client):
6
+ calc_tool = FunctionTool(calculate_strategy_metrics, description="Deterministic math engine. Requires 'legs' (list of dicts with action, type, strike, price, expiry) and 'spot_price' (float).")
7
+
8
+ return AssistantAgent(
9
+ name="RiskManager",
10
+ model_client=model_client,
11
+ tools=[calc_tool],
12
+ system_message="""
13
+ You are the Chief Risk Officer and Lead Critic. You do not just validate; you find flaws and demand excellence.
14
+
15
+ MANDATORY 2-ROUND WORKFLOW:
16
+
17
+ ROUND 1: THE CRITIQUE
18
+ 1. ANALYZE the StrategyAdvisor's DRAFT_STRATEGY.
19
+ 2. CRITIQUE blocks:
20
+ - MATH: Is the P/L claim realistic? (Do not call tool yet, just use intuition).
21
+ - REGIME: Does this strategy match the VolatilityAnalyst's findings?
22
+ - EVENT: Did they ignore an earnings date from the FundamentalAnalyst?
23
+ 3. OUTPUT: "CRITIQUE: [Detailed feedback points]" or "PROVISIONALLY APPROVED: Proceed to final math."
24
+
25
+ ROUND 2: THE FINAL VERDICT
26
+ 1. MANDATORY MATH VERIFICATION: Call `calculate_strategy_metrics`.
27
+ - EXAMPLE: `calculate_strategy_metrics(legs=[{"action": "BUY", "type": "CALL", "strike": 100, "price": 5, "expiry": "2024-03-01"}], spot_price=105.5)`
28
+ - You MUST extract the `legs` and `spot_price` from the StrategyAdvisor's message.
29
+ 3. VERIFY the output matches StrategyAdvisor's final claims.
30
+ - CHECK: Ensure `actionable_recommendation` explicitly lists each leg (Strike, Type, Expiry).
31
+ 4. SCORING (STRICT):
32
+ - Technicals (40 pts), Fundamentals (20 pts), Volatility (20 pts), Event Risk (20 pts).
33
+ 4. BE FAST: Use bullet points. No conversational filler.
34
+ 5. NO PLEASANTRIES: Do not say "Thank you" or "You're welcome".
35
+ 6. IF SATISFIED: Output the word "APPROVED" followed by the final JSON immediately.
36
+ 7. IF Still flawed: Suggest "WAIT" and output JSON with decision "WAIT".
37
+
38
+ FINAL JSON SCHEMA:
39
+ {
40
+ "ticker": "...",
41
+ "final_decision": "TRADE/WAIT",
42
+ "strategy_type": "...",
43
+ "confidence": 85,
44
+ "entry_signal": "...",
45
+ "entry_price": 1.25,
46
+ "max_profit": 200,
47
+ "max_loss": 125,
48
+ "legs": [...],
49
+ "score_card": { "technicals": 40, "fundamentals": 20, "volatility": 15, "sentiment": 10, "total": 85 },
50
+ "actionable_recommendation": "...",
51
+ "risk_warning": "..."
52
+ }
53
+ """
54
+ )
src/agora/backend/aagents/sentiment_analyst.py ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.news_data import search_news
4
+
5
+ def get_sentiment_analyst(model_client):
6
+
7
+ news_tool = FunctionTool(search_news, description="Search for recent news about the ticker with FinBERT sentiment scores.")
8
+
9
+ return AssistantAgent(
10
+ name="SentimentAnalyst",
11
+ model_client=model_client,
12
+ tools=[news_tool],
13
+ system_message="""
14
+ You are a Sentiment Analyst specializing in market psychology and news analysis.
15
+
16
+ MANDATORY WORKFLOW:
17
+
18
+ STEP 1: CALL search_news to get recent news articles with FinBERT sentiment scores
19
+
20
+ DO NOT proceed without calling the tool first.
21
+
22
+ STEP 2: Aggregate & Categorize News Events
23
+ - Identify "Binary Events": Earnings, FDA approvals, Court rulings, Mergers.
24
+ - Identify "Macro Events": Fed news, Inflation, Sector rotation.
25
+ - Rank articles by "Impact Potential" (e.g., Earnings > General News).
26
+
27
+ STEP 3: Determine Overall Sentiment using FinBERT
28
+ - Each article has a FinBERT score like [FinBERT: positive (0.95)]
29
+ - If Binary Events are "Negative", they OVERRIDE general "Neutral" sentiment.
30
+
31
+ STEP 4: Assign Sentiment Level
32
+ - "Strongly Bullish": Coherent positive news across top sources.
33
+ - "Bearish (Event-Driven)": Negative binary news detected.
34
+
35
+ STEP 5: Evaluate Sentiment Confidence vs Time
36
+ - HIGHER weight for news within last 48 hours.
37
+
38
+ STEP 6: Output Structured Summary
39
+ - BE CONCISE: Use maximum 5 bullet points.
40
+ - NO conversational filler.
41
+ - Include: Sentiment Status, Key Binary Events, Primary Risks, and strategy impact.
42
+ """
43
+ )
src/agora/backend/aagents/strategy_advisor.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.market_data import get_option_chain_snapshot, get_available_expirations
4
+
5
+ def get_strategy_advisor(model_client):
6
+ chain_tool = FunctionTool(get_option_chain_snapshot, description="Get option chain for a specific expiry.")
7
+ exp_tool = FunctionTool(get_available_expirations, description="Get all available option expiration dates.")
8
+
9
+ return AssistantAgent(
10
+ name="StrategyAdvisor",
11
+ model_client=model_client,
12
+ tools=[chain_tool, exp_tool],
13
+ system_message="""
14
+ You are an Expert Multi-Leg Option Strategist with high self-awareness. You design complex spreads and refine them through self-reflection.
15
+
16
+ MANDATORY HIERARCHICAL WORKFLOW (Team 2):
17
+
18
+ 1. STUDY ANALYST CONTEXT: You will receive a summary from Phase 1.
19
+ 2. CALL DATA TOOLS: Use `get_available_expirations` and `get_option_chain_snapshot`.
20
+ 2. CALL DATA TOOLS: Use `get_available_expirations` and `get_option_chain_snapshot`.
21
+ - TOOL UPGRADE: `get_option_chain_snapshot(ticker)` now returns a COMPARATIVE VIEW of 3 expiries (15-45 days).
22
+ - TASK: Compare all 3 expiries. Analyze the Risk/Reward for a strategy on each date.
23
+ - SELECTION: Choose the single BEST expiry that offers the highest Probability of Profit (POP) and favorable Risk/Reward.
24
+ 3. DESIGN STRATEGY: Propose a multi-leg strategy.
25
+ - CRITICAL: You MUST include a `DRAFT_STRATEGY_LEGS` block:
26
+ DRAFT_STRATEGY_LEGS:
27
+ [{"action": "BUY", "type": "CALL", "strike": 150.0, "price": 2.5, "expiry": "2024-03-01"}, ...]
28
+ 4. BE FAST: Skip lengthy reasoning in the draft. Go straight to the legs.
29
+ 5. NO PLEASANTRIES: Do not say "Thank you", "I understand", or "You're welcome".
30
+ 6. FINALIZE: When RiskManager approves, output your final strategy inside a `FINAL_STRATEGY` block.
31
+ - CRITICAL: The `actionable_recommendation` field MUST explicitly list each leg with its type, strike, and EXACT expiry date.
32
+
33
+ JSON SCHEMA (ROUND 2 ONLY):
34
+ {
35
+ "ticker": "...",
36
+ "final_decision": "TRADE/WAIT",
37
+ "actionable_recommendation": "EXPLAIN EACH LEG: 'Buy $150 Call (Exp 2024-03-01), Sell $155 Call (Exp 2024-03-01)...'",
38
+ "strategy_type": "...",
39
+ "direction": "BULLISH/BEARISH/NEUTRAL",
40
+ "confidence": 85,
41
+ "reasoning": "...",
42
+ "entry_signal": "Net Debit/Credit",
43
+ "entry_price": 1.25,
44
+ "max_profit": 200,
45
+ "max_loss": 125,
46
+ "legs": [
47
+ {
48
+ "action": "SELL",
49
+ "type": "CALL",
50
+ "strike": 150,
51
+ "expiry": "2024-03-01",
52
+ "price": 2.50
53
+ },
54
+ {
55
+ "action": "BUY",
56
+ "type": "CALL",
57
+ "strike": 150,
58
+ "expiry": "2024-03-15",
59
+ "price": 3.75
60
+ }
61
+ ],
62
+ "risk_warning": "..."
63
+ }
64
+
65
+ CRITICAL: All profit/loss values MUST be multiplied by 100 per contract.
66
+ """
67
+ )
src/agora/backend/aagents/volatility_analyst.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from autogen_agentchat.agents import AssistantAgent
2
+ from autogen_core.tools import FunctionTool
3
+ from tools.market_data import get_historical_volatility, get_option_chain_snapshot, get_volatility_term_structure
4
+
5
+ def get_volatility_analyst(model_client):
6
+ vol_tool = FunctionTool(get_historical_volatility, description="Get historical volatility and VIX context.")
7
+ chain_tool = FunctionTool(get_option_chain_snapshot, description="Get option chain snapshot for near-term expiry.")
8
+ term_tool = FunctionTool(get_volatility_term_structure, description="Get IV across multiple expiries to identify Term Structure skew.")
9
+
10
+ return AssistantAgent(
11
+ name="VolatilityAnalyst",
12
+ model_client=model_client,
13
+ tools=[vol_tool, chain_tool, term_tool],
14
+ system_message="""
15
+ You are an Expert Volatility & Derivatives Analyst specializing in Volatility Surface and Term Structure.
16
+
17
+ MANDATORY WORKFLOW:
18
+
19
+ STEP 1: CALL get_historical_volatility to get HV and VIX data
20
+ STEP 2: CALL get_volatility_term_structure to analyze IV across 4 months of expiries
21
+ STEP 3: CALL get_option_chain_snapshot to get near-term IV and liquidity
22
+
23
+ DO NOT proceed without calling ALL THREE tools first.
24
+
25
+ STEP 4: Analyze IV vs HV (Vertical Skew)
26
+ - IV > HV: Options are rich. Look for Credit Spreads, Iron Condors.
27
+ - IV < HV: Options are cheap. Look for Debit Spreads, Long Options.
28
+
29
+ STEP 5: Analyze Term Structure (Horizontal/Time Skew)
30
+ - FRONT IV > BACK IV (Inverted): Potential "Calendar Spread" (Sell Front, Buy Back) if you expect a mean reversion.
31
+ - BACK IV > FRONT IV (Contango): Standard. Long-dated options are more expensive.
32
+
33
+ STEP 6: Assess "Volatility Squeeze"
34
+ - If IV is at 52-week lows and HV is dropping: Potential for a volatility breakout. Recommend DEBIT strategies.
35
+
36
+ STEP 7: Output Structured Summary
37
+ - BE CONCISE: Use maximum 5 bullet points.
38
+ - NO conversational filler.
39
+ - Include: Volatility Regime, IV vs HV status, Term Structure summary, and strategy bias.
40
+ """
41
+ )
src/agora/backend/main.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from fastapi.responses import StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.staticfiles import StaticFiles
5
+ import asyncio
6
+ import json
7
+ import os
8
+ import sys
9
+ from typing import AsyncGenerator
10
+
11
+ from utility.autogen_model_factory import AutoGenModelFactory
12
+ from teams.team import get_analyst_team, get_decision_team, extract_json
13
+ from tools.news_data import get_sentiment_pipeline
14
+
15
+ app = FastAPI(title="Agora API")
16
+
17
+ app.add_middleware(
18
+ CORSMiddleware,
19
+ allow_origins=["*"],
20
+ allow_credentials=True,
21
+ allow_methods=["*"],
22
+ allow_headers=["*"],
23
+ )
24
+
25
+ # Track active analyses for cancellation
26
+ active_analyses = {}
27
+
28
+ @app.on_event("startup")
29
+ async def startup_event():
30
+ print("Warming up FinBERT...")
31
+ get_sentiment_pipeline()
32
+
33
+ @app.get("/health")
34
+ async def health():
35
+ return {"status": "healthy"}
36
+
37
+ @app.post("/cancel/{analysis_id}")
38
+ async def cancel_analysis(analysis_id: str):
39
+ if analysis_id in active_analyses:
40
+ active_analyses[analysis_id] = True
41
+ return {"status": "cancelled", "analysis_id": analysis_id}
42
+ return {"status": "not_found", "analysis_id": analysis_id}
43
+
44
+ @app.get("/analyze")
45
+ async def analyze(ticker: str, provider: str = "openai"):
46
+ import uuid
47
+ analysis_id = str(uuid.uuid4())
48
+ active_analyses[analysis_id] = False
49
+
50
+ async def event_generator() -> AsyncGenerator[str, None]:
51
+ if provider == "openai":
52
+ model_name = "gpt-4o"
53
+ family = "gpt"
54
+ elif provider == "groq":
55
+ model_name = "llama-3.3-70b-versatile"
56
+ family = "groq"
57
+ elif provider == "google":
58
+ model_name = "gemini-pro-latest"
59
+ family = "gemini"
60
+ else:
61
+ model_name = "gpt-4o"
62
+ family = "gpt"
63
+
64
+ try:
65
+ model_client = AutoGenModelFactory.get_model(
66
+ provider=provider,
67
+ model_name=model_name,
68
+ temperature=0,
69
+ model_info={
70
+ "family": family,
71
+ "vision": False,
72
+ "function_calling": True,
73
+ "json_output": True,
74
+ "structured_output": True if provider == "openai" else False
75
+ }
76
+ )
77
+ except Exception as e:
78
+ yield f"data: {json.dumps({'error': f'Model initialization failed: {str(e)}'})}\n\n"
79
+ return
80
+
81
+ # PHASE 1: Analysts
82
+ yield f"data: {json.dumps({'source': 'System', 'content': 'PHASE 1: Starting Data Collection for ' + ticker.upper(), 'analysis_id': analysis_id})}\n\n"
83
+ analyst_team = get_analyst_team(model_client)
84
+ phase1_task = f"Perform complete analyst data collection for {ticker.upper()}."
85
+ analyst_context = []
86
+
87
+ try:
88
+ async for message in analyst_team.run_stream(task=phase1_task):
89
+ if active_analyses.get(analysis_id, False): break
90
+ raw_source = getattr(message, 'source', 'System')
91
+ content = getattr(message, 'content', '')
92
+ if not content or raw_source == 'User': continue
93
+ analyst_context.append(f"[{raw_source}]: {content}")
94
+ yield f"data: {json.dumps({'source': raw_source, 'content': str(content)})}\n\n"
95
+ except Exception as e:
96
+ yield f"data: {json.dumps({'source': 'Error', 'content': f'Phase 1 bug: {str(e)}'})}\n\n"
97
+
98
+ # PHASE 2: Decision
99
+ yield f"data: {json.dumps({'source': 'System', 'content': 'PHASE 2: Designing Strategy...'})}\n\n"
100
+ market_context_str = "\n\n".join(analyst_context)
101
+ decision_team = get_decision_team(model_client)
102
+ phase2_task = f"ANALYST CONTEXT:\n{market_context_str}\n\nGOAL: Design, critique, and finalize trade for {ticker}. Only the LeadOrchestrator can end the cycle."
103
+
104
+ try:
105
+ async for message in decision_team.run_stream(task=phase2_task):
106
+ if active_analyses.get(analysis_id, False): break
107
+ raw_source = getattr(message, 'source', 'System')
108
+ content = getattr(message, 'content', '')
109
+ if not content or (raw_source == 'User' and "ANALYST CONTEXT" in content): continue
110
+ payload = {"source": raw_source, "content": str(content)}
111
+ if raw_source in ['RiskManager', 'LeadOrchestrator']:
112
+ structured = extract_json(str(content))
113
+ if structured: payload["structured_result"] = structured
114
+ yield f"data: {json.dumps(payload)}\n\n"
115
+ except Exception as e:
116
+ yield f"data: {json.dumps({'source': 'Error', 'content': f'Phase 2 bug: {str(e)}'})}\n\n"
117
+
118
+ if analysis_id in active_analyses: del active_analyses[analysis_id]
119
+ yield "data: [DONE]\n\n"
120
+
121
+ return StreamingResponse(event_generator(), media_type="text/event-stream")
122
+
123
+ # Static mounting logic...
124
+ frontend_dist = os.path.abspath(os.path.join(current_dir, "../frontend/dist"))
125
+ if os.path.exists(frontend_dist):
126
+ assets_dir = os.path.join(frontend_dist, "assets")
127
+ if os.path.exists(assets_dir):
128
+ app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
129
+ from fastapi.responses import FileResponse
130
+ @app.get("/{rest_of_path:path}")
131
+ async def serve_frontend(rest_of_path: str):
132
+ file_path = os.path.join(frontend_dist, rest_of_path)
133
+ if os.path.isfile(file_path): return FileResponse(file_path)
134
+ return FileResponse(os.path.join(frontend_dist, "index.html"))
135
+ else:
136
+ @app.get("/")
137
+ async def root(): return {"message": "API running. Frontend missing."}
138
+
139
+ if __name__ == "__main__":
140
+ import uvicorn
141
+ uvicorn.run(app, host="0.0.0.0", port=8000)
src/agora/backend/requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ python-dotenv
4
+ beautifulsoup4
5
+ requests
6
+ pydantic
7
+ autogen-agentchat==0.4.7
8
+ autogen-ext[grpc,mcp,ollama,openai]==0.4.7
9
+ transformers
10
+ torch
11
+ duckduckgo_search
12
+ pandas
src/agora/backend/teams/__init__.py ADDED
File without changes
src/agora/backend/teams/team.py ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ import os
3
+ import json
4
+ import re
5
+ from typing import Dict, Any
6
+
7
+ # Ensure we can import from parent directory if needed
8
+ current_dir = os.path.dirname(os.path.abspath(__file__))
9
+ parent_dir = os.path.abspath(os.path.join(current_dir, ".."))
10
+ if parent_dir not in sys.path:
11
+ sys.path.append(parent_dir)
12
+
13
+ from autogen_agentchat.teams import SelectorGroupChat
14
+ from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination, HandoffTermination
15
+
16
+ # Import agents
17
+ # Adjust imports to work whether called from here or app.py
18
+ try:
19
+ from ..aagents.market_analyst import get_technical_analyst
20
+ from ..aagents.volatility_analyst import get_volatility_analyst
21
+ from ..aagents.sentiment_analyst import get_sentiment_analyst
22
+ from ..aagents.strategy_advisor import get_strategy_advisor
23
+ from ..aagents.risk_manager import get_risk_manager
24
+ from ..aagents.fundamental_analyst import get_fundamental_analyst
25
+ from ..aagents.orchestrator import get_lead_orchestrator
26
+ except ImportError:
27
+ try:
28
+ from aagents.market_analyst import get_technical_analyst
29
+ from aagents.volatility_analyst import get_volatility_analyst
30
+ from aagents.sentiment_analyst import get_sentiment_analyst
31
+ from aagents.strategy_advisor import get_strategy_advisor
32
+ from aagents.risk_manager import get_risk_manager
33
+ from aagents.fundamental_analyst import get_fundamental_analyst
34
+ from aagents.orchestrator import get_lead_orchestrator
35
+ except ImportError:
36
+ # Try absolute (if market-analyst is in path but not as package)
37
+ from src.market_analyst.backend.aagents.market_analyst import get_technical_analyst
38
+ from src.market_analyst.backend.aagents.volatility_analyst import get_volatility_analyst
39
+ from src.market_analyst.backend.aagents.sentiment_analyst import get_sentiment_analyst
40
+ from src.market_analyst.backend.aagents.strategy_advisor import get_strategy_advisor
41
+ from src.market_analyst.backend.aagents.risk_manager import get_risk_manager
42
+ from src.market_analyst.backend.aagents.fundamental_analyst import get_fundamental_analyst
43
+
44
+ from autogen_agentchat.teams import SelectorGroupChat, RoundRobinGroupChat
45
+
46
+ def get_analyst_team(model_client):
47
+ """
48
+ Team 1: DATA COLLECTORS.
49
+ Independent analysts gather data and provide a comprehensive market snapshot.
50
+ """
51
+ technical = get_technical_analyst(model_client)
52
+ volatility = get_volatility_analyst(model_client)
53
+ sentiment = get_sentiment_analyst(model_client)
54
+ fundamental = get_fundamental_analyst(model_client)
55
+
56
+ return RoundRobinGroupChat(
57
+ participants=[technical, volatility, sentiment, fundamental],
58
+ termination_condition=TextMentionTermination("[[DATA_COLLECTION_COMPLETE]]") | MaxMessageTermination(15)
59
+ )
60
+
61
+ def get_decision_team(model_client):
62
+ """
63
+ Team 2: STRATEGY & RISK.
64
+ Uses the Analyst Context (Team 1 output) to design, critique, and finalize the trade.
65
+ """
66
+ strategy = get_strategy_advisor(model_client)
67
+ risk = get_risk_manager(model_client)
68
+ orchestrator = get_lead_orchestrator(model_client)
69
+
70
+ participants = [strategy, risk, orchestrator]
71
+
72
+ selector_prompt = """
73
+ Select the next agent based on the conversation history:
74
+ - Choose StrategyAdvisor to propose or update the trade.
75
+ - Choose RiskManager to verify the proposal or critique it.
76
+ - Choose LeadOrchestrator ONLY if the RiskManager has said "APPROVED" or if the discussion is stuck.
77
+
78
+ Output only the name of the next agent.
79
+ """
80
+
81
+ return SelectorGroupChat(
82
+ participants=participants,
83
+ model_client=model_client,
84
+ termination_condition=TextMentionTermination("[[ANALYSIS_JUDGMENT_COMPLETE]]") | MaxMessageTermination(12),
85
+ selector_prompt=selector_prompt
86
+ )
87
+
88
+ def extract_json(text: str) -> Dict[str, Any]:
89
+ """
90
+ Extract JSON from text that may contain markdown code blocks or other content.
91
+ Validates and ensures required fields are present.
92
+ """
93
+ # Try to find JSON in code blocks first
94
+ json_block_pattern = r'```(?:json)?\s*(\{.*?\})\s*```'
95
+ matches = re.findall(json_block_pattern, text, re.DOTALL)
96
+ if matches:
97
+ try:
98
+ parsed = json.loads(matches[-1])
99
+ return validate_and_complete_json(parsed)
100
+ except json.JSONDecodeError:
101
+ pass
102
+
103
+ # Try to find raw JSON object
104
+ json_pattern = r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}'
105
+ matches = re.findall(json_pattern, text, re.DOTALL)
106
+ if matches:
107
+ for match in reversed(matches):
108
+ try:
109
+ parsed = json.loads(match)
110
+ return validate_and_complete_json(parsed)
111
+ except json.JSONDecodeError:
112
+ continue
113
+
114
+ return {}
115
+
116
+ def validate_and_complete_json(data: Dict[str, Any]) -> Dict[str, Any]:
117
+ """
118
+ Validate JSON has required fields and add defaults if missing.
119
+ """
120
+ required_fields = {
121
+ "final_decision": "WAIT",
122
+ "confidence": 0,
123
+ "actionable_recommendation": "Incomplete analysis - please retry",
124
+ "strategy_type": "None",
125
+ "entry_signal": "N/A",
126
+ "entry_price": 0,
127
+ "max_profit": 0,
128
+ "max_loss": 0,
129
+ "risk_warning": "Analysis incomplete",
130
+ "expiry_date": "N/A",
131
+ "legs": []
132
+ }
133
+
134
+ # Add missing required fields with defaults
135
+ for field, default_value in required_fields.items():
136
+ if field not in data:
137
+ data[field] = default_value
138
+
139
+ # Fallback: If expiry_date is "N/A" but we have legs, take it from there
140
+ if data.get("expiry_date") == "N/A" and data.get("legs"):
141
+ # Take expiry of first leg
142
+ data["expiry_date"] = data["legs"][0].get("expiry", "N/A")
143
+
144
+ # Existing Fallback: If expiry_date is still "N/A", try to extract it from context
145
+ if data.get("expiry_date") == "N/A":
146
+ # Look for YYYY-MM-DD pattern
147
+ date_pattern = r'\d{4}-\d{2}-\d{2}'
148
+
149
+ # Check 'actionable_recommendation' or 'reasoning' (if present)
150
+ for search_field in ["actionable_recommendation", "reasoning", "risk_warning", "proposed_legs"]:
151
+ field_val = data.get(search_field, "")
152
+ if isinstance(field_val, str):
153
+ match = re.search(date_pattern, field_val)
154
+ if match:
155
+ data["expiry_date"] = match.group(0)
156
+ break
157
+
158
+ return data
src/agora/backend/tools/__init__.py ADDED
File without changes
src/agora/backend/tools/market_data.py ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import yfinance as yf
2
+ import pandas as pd
3
+ import math
4
+ from datetime import datetime, timedelta
5
+
6
+ def normalize_period(period: str) -> str:
7
+ """Standardizes period strings for yfinance."""
8
+ p = period.lower().strip()
9
+ if p in ["1yr", "1year"]: return "1y"
10
+ if p in ["3mo", "3month"]: return "3mo"
11
+ if p in ["1mo", "1month"]: return "1mo"
12
+ if p in ["1wk", "1week"]: return "1wk"
13
+ return p
14
+
15
+ def check_and_fix_ticker(symbol: str) -> str:
16
+ """
17
+ Checks if the ticker has data. If not, tries appending '.NS' (for NSE India).
18
+ Returns the working ticker or the original if neither works.
19
+ """
20
+ print(f"[DEBUG] check_and_fix_ticker called using: {symbol}")
21
+ symbol = symbol.upper().strip()
22
+
23
+ # Try original
24
+ ticker = yf.Ticker(symbol)
25
+ try:
26
+ # fast_info is quick way to check validity
27
+ if ticker.fast_info.last_price is not None:
28
+ return symbol
29
+ except:
30
+ pass
31
+
32
+ # Try .NS
33
+ ns_symbol = symbol + ".NS"
34
+ ns_ticker = yf.Ticker(ns_symbol)
35
+ try:
36
+ if ns_ticker.fast_info.last_price is not None:
37
+ return ns_symbol
38
+ except:
39
+ pass
40
+
41
+ return symbol
42
+
43
+ def get_current_price(symbol: str) -> float:
44
+ """Fetches the current market price of the stock."""
45
+ print(f"[DEBUG] get_current_price called for: {symbol}")
46
+ try:
47
+ # Auto-fix ticker if needed
48
+ symbol = check_and_fix_ticker(symbol)
49
+
50
+ ticker = yf.Ticker(symbol)
51
+ price = ticker.fast_info.last_price
52
+ if price is None:
53
+ hist = ticker.history(period="1d")
54
+ if not hist.empty:
55
+ price = hist["Close"].iloc[-1]
56
+
57
+ if price is None:
58
+ return f"Error: No price data for {symbol}"
59
+
60
+ return price
61
+ except Exception as e:
62
+ return f"Error fetching price for {symbol}: {str(e)}"
63
+
64
+ def get_historical_volatility(symbol: str, period: str = "1mo") -> dict:
65
+ """
66
+ Calculates historical volatility and returns VIX context if available.
67
+ """
68
+ print(f"[DEBUG] get_historical_volatility called for: {symbol}")
69
+ try:
70
+ symbol = check_and_fix_ticker(symbol)
71
+ period = normalize_period(period)
72
+ ticker = yf.Ticker(symbol)
73
+ hist = ticker.history(period=period)
74
+ if hist.empty:
75
+ return {"error": "No historical data found"}
76
+
77
+ hist['Returns'] = hist['Close'].pct_change()
78
+ volatility = hist['Returns'].std() * math.sqrt(252)
79
+
80
+ vix_price = None
81
+ try:
82
+ vix = yf.Ticker("^VIX")
83
+ vix_price = vix.fast_info.last_price
84
+ except:
85
+ pass
86
+
87
+ return {
88
+ "ticker_used": symbol,
89
+ "annualized_volatility": round(volatility * 100, 2),
90
+ "period": period,
91
+ "vix_reference": round(vix_price, 2) if vix_price else "N/A",
92
+ "volatility_regime": "HIGH_RISK_VOL" if (vix_price and vix_price > 30) else "ELEVATED_VOL" if (vix_price and vix_price > 20) else "LOW_VOL" if (vix_price and vix_price < 15) else "NORMAL_VOL"
93
+ }
94
+ except Exception as e:
95
+ return {"error": str(e)}
96
+
97
+ def get_available_expirations(symbol: str) -> list:
98
+ """Returns a list of available option expiration dates."""
99
+ try:
100
+ symbol = check_and_fix_ticker(symbol)
101
+ ticker = yf.Ticker(symbol)
102
+ exps = list(ticker.options)
103
+
104
+ # Add context about DTE (Days Till Expiration) to help agents choose
105
+ today = datetime.now()
106
+ annotated = []
107
+ best_expiry = "None"
108
+ min_diff = 999
109
+
110
+ for e in exps:
111
+ try:
112
+ dt = datetime.strptime(e, "%Y-%m-%d")
113
+ days = (dt - today).days
114
+
115
+ # Logic: Find valid targets between 15-45 days, prioritize earliest
116
+ if 15 <= days <= 45:
117
+ note = f"({days} DTE) [VALID]"
118
+ annotated.append(f"{e} {note}")
119
+
120
+ if days < min_diff:
121
+ min_diff = days
122
+ best_expiry = e
123
+ except:
124
+ pass
125
+
126
+ return [f"RECOMMENDED: {best_expiry} (Earliest in 15-45d window)"] + annotated
127
+ except Exception as e:
128
+ return []
129
+
130
+ def get_option_chain_snapshot(symbol: str, target_date: str = None) -> str:
131
+ """
132
+ Fetches a comparative snapshot of option chains for up to 3 valid expiries (15-45 DTE).
133
+ If target_date is provided, fetches only that specific date.
134
+ If target_date is None, automatically selects Short (15-25d), Mid (25-35d), and Long (35-45d) targets.
135
+ """
136
+ print(f"[DEBUG] get_option_chain_snapshot called for: {symbol} (Target: {target_date})")
137
+ try:
138
+ symbol = check_and_fix_ticker(symbol)
139
+ ticker = yf.Ticker(symbol)
140
+ expirations = ticker.options
141
+
142
+ if not expirations:
143
+ return f"No options data found for {symbol}."
144
+
145
+ target_dates = []
146
+ if target_date:
147
+ if target_date in expirations:
148
+ target_dates = [target_date]
149
+ else:
150
+ return f"Target date {target_date} not found in expirations."
151
+ else:
152
+ # Auto-select up to 3 expiries in 15-45 day window
153
+ today = datetime.now()
154
+ valid_exps = []
155
+ for exp in expirations:
156
+ dt = datetime.strptime(exp, "%Y-%m-%d")
157
+ days = (dt - today).days
158
+ if 15 <= days <= 45:
159
+ valid_exps.append(exp)
160
+
161
+ # Pick Short, Mid, Long from valid list
162
+ if not valid_exps:
163
+ # Fallback to nearest if no window match
164
+ target_dates = [expirations[0]]
165
+ else:
166
+ # Simple distribution: First, Middle, Last
167
+ target_dates = list(sorted(set([valid_exps[0], valid_exps[len(valid_exps)//2], valid_exps[-1]])))
168
+
169
+ price_info = get_current_price(symbol)
170
+ if isinstance(price_info, str): return price_info
171
+ current_price = float(price_info)
172
+
173
+ final_summary = f"COMPARATIVE OPTION ANALYSIS for {symbol} (Spot: {round(current_price, 2)})\n"
174
+
175
+ for date in target_dates:
176
+ dt = datetime.strptime(date, "%Y-%m-%d")
177
+ days = (dt - datetime.now()).days
178
+
179
+ opt = ticker.option_chain(date)
180
+ calls = opt.calls
181
+ puts = opt.puts
182
+
183
+ # Filter around ATM (tight range for comparison)
184
+ ntm_calls = calls.iloc[(calls['strike'] - current_price).abs().argsort()[:4]].sort_values('strike')
185
+ ntm_puts = puts.iloc[(puts['strike'] - current_price).abs().argsort()[:4]].sort_values('strike')
186
+
187
+ final_summary += f"\n>>> EXPIRY: {date} ({days} DTE) <<<\n"
188
+
189
+ # Calls
190
+ for _, row in ntm_calls.iterrows():
191
+ ask = row.get('ask', 0.0)
192
+ last = row.get('lastPrice', 0.0)
193
+ price = ask if ask > 0 else last
194
+ iv = round(row['impliedVolatility']*100, 1) if not pd.isna(row.get('impliedVolatility')) else 0
195
+ final_summary += f"[CALL] Strike {row['strike']} | Price ${price} | IV {iv}%\n"
196
+
197
+ # Puts
198
+ for _, row in ntm_puts.iterrows():
199
+ ask = row.get('ask', 0.0)
200
+ last = row.get('lastPrice', 0.0)
201
+ price = ask if ask > 0 else last
202
+ iv = round(row['impliedVolatility']*100, 1) if not pd.isna(row.get('impliedVolatility')) else 0
203
+ final_summary += f"[PUT ] Strike {row['strike']} | Price ${price} | IV {iv}%\n"
204
+
205
+ return final_summary
206
+
207
+ except Exception as e:
208
+ return f"Error fetching option chain: {str(e)}"
209
+
210
+ def get_volatility_term_structure(symbol: str) -> str:
211
+ """
212
+ Analyzes IV across multiple expiries to identify Term Structure skew.
213
+ """
214
+ print(f"[DEBUG] get_volatility_term_structure called for: {symbol}")
215
+ try:
216
+ symbol = check_and_fix_ticker(symbol)
217
+ ticker = yf.Ticker(symbol)
218
+ expirations = ticker.options[:4] # Check first 4 expiries
219
+
220
+ if not expirations:
221
+ return "No options data for volatility analysis."
222
+
223
+ results = []
224
+ for exp in expirations:
225
+ opt = ticker.option_chain(exp)
226
+ # Use mean IV of ATM calls
227
+ calls = opt.calls
228
+ price_info = get_current_price(symbol)
229
+ if isinstance(price_info, str): continue
230
+ current_price = float(price_info)
231
+ atm_iv = calls.iloc[(calls['strike'] - current_price).abs().argsort()[:2]]['impliedVolatility'].mean()
232
+ results.append(f"- {exp}: {round(atm_iv * 100, 1)}% IV")
233
+
234
+ summary = f"VOLATILITY TERM STRUCTURE for {symbol}:\n" + "\n".join(results)
235
+
236
+ # Analyze skew
237
+ if len(expirations) >= 2:
238
+ try:
239
+ iv1 = float(results[0].split(": ")[1].replace("% IV", ""))
240
+ iv2 = float(results[1].split(": ")[1].replace("% IV", ""))
241
+ if iv1 > iv2 + 5:
242
+ summary += f"\n\nSKEW ALERT: Front-month IV is significantly HIGHER ({iv1}% vs {iv2}%). Potential for Calendar Spreads (Sell Front, Buy Back)."
243
+ elif iv1 < iv2 - 5:
244
+ summary += f"\n\nSKEW ALERT: Front-month IV is significantly LOWER ({iv1}% vs {iv2}%). Diagonal opportunities."
245
+ else:
246
+ summary += f"\n\nTerm Structure is relatively flat."
247
+ except: pass
248
+
249
+ return summary
250
+ except Exception as e:
251
+ return f"Error analyzing term structure: {str(e)}"
252
+
253
+ def get_market_indices() -> str:
254
+ """
255
+ Fetches current market context using SPY (S&P 500) and ^VIX.
256
+ Returns trend (Price vs SMA50) and Volatility regime.
257
+ """
258
+ try:
259
+ tickers = yf.Tickers("SPY ^VIX")
260
+ spy = tickers.tickers['SPY']
261
+ vix = tickers.tickers['^VIX']
262
+
263
+ # SPY Data
264
+ spy_hist = spy.history(period="3mo")
265
+ if spy_hist.empty: return "Market data unavailable."
266
+
267
+ current_spy = spy_hist['Close'].iloc[-1]
268
+ spy_sma50 = spy_hist['Close'].rolling(window=50).mean().iloc[-1]
269
+ spy_trend = "BULLISH" if current_spy > spy_sma50 else "BEARISH"
270
+
271
+ # VIX Data
272
+ vix_hist = vix.history(period="1d")
273
+ current_vix = vix_hist['Close'].iloc[-1] if not vix_hist.empty else 0
274
+
275
+ vix_regime = "LOW"
276
+ if current_vix > 20: vix_regime = "ELEVATED"
277
+ if current_vix > 30: vix_regime = "HIGH/FEAR"
278
+
279
+ return f"MARKET CONTEXT:\n- SPY Trend: {spy_trend} (Price: {round(current_spy, 2)} vs SMA50: {round(spy_sma50, 2)})\n- VIX Level: {round(current_vix, 2)} ({vix_regime})"
280
+
281
+ except Exception as e:
282
+ return f"Error fetching market indices: {str(e)}"
283
+
284
+ def get_technical_indicators(symbol: str) -> dict:
285
+ """
286
+ Calculates key technical indicators: SMA (20, 50, 200) and RSI (14).
287
+ Returns a dictionary of values and signals.
288
+ """
289
+ print(f"[DEBUG] get_technical_indicators called for: {symbol}")
290
+ try:
291
+ symbol = check_and_fix_ticker(symbol)
292
+ ticker = yf.Ticker(symbol)
293
+
294
+ # Fetch enough history for SMA 200
295
+ hist = ticker.history(period="1y")
296
+ if hist.empty:
297
+ return {"error": "No historical data found"}
298
+
299
+ # Safe calculations
300
+ # SMA
301
+ hist['SMA_20'] = hist['Close'].rolling(window=20).mean()
302
+ hist['SMA_50'] = hist['Close'].rolling(window=50).mean()
303
+ hist['EMA_20'] = hist['Close'].ewm(span=20, adjust=False).mean()
304
+
305
+ # MACD (12, 26, 9)
306
+ exp1 = hist['Close'].ewm(span=12, adjust=False).mean()
307
+ exp2 = hist['Close'].ewm(span=26, adjust=False).mean()
308
+ hist['MACD'] = exp1 - exp2
309
+ hist['Signal_Line'] = hist['MACD'].ewm(span=9, adjust=False).mean()
310
+
311
+ # Only calc SMA_200 if we actually have 200 points
312
+ if len(hist) >= 200:
313
+ hist['SMA_200'] = hist['Close'].rolling(window=200).mean()
314
+ else:
315
+ hist['SMA_200'] = pd.Series([None] * len(hist), index=hist.index)
316
+
317
+ # RSI Calculation
318
+ if len(hist) >= 15:
319
+ delta = hist['Close'].diff()
320
+ gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
321
+ loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
322
+ rs = gain / loss
323
+ hist['RSI_14'] = 100 - (100 / (1 + rs))
324
+ else:
325
+ hist['RSI_14'] = pd.Series([None] * len(hist), index=hist.index)
326
+
327
+ current_data = hist.iloc[-1]
328
+ price = current_data['Close']
329
+ rsi_val = current_data.get('RSI_14')
330
+ macd_val = current_data.get('MACD')
331
+ signal_val = current_data.get('Signal_Line')
332
+ sma200 = current_data.get('SMA_200')
333
+ if pd.isna(sma200): sma200 = None
334
+
335
+ # Interpretation RSI
336
+ # Deterministic Signals
337
+ rsi_signal = "NEUTRAL"
338
+ if rsi_val is not None and not pd.isna(rsi_val):
339
+ if rsi_val > 70: rsi_signal = "OVERBOUGHT"
340
+ elif rsi_val < 30: rsi_signal = "OVERSOLD"
341
+
342
+ macd_signal = "NEUTRAL"
343
+ if macd_val is not None and signal_val is not None and not pd.isna(macd_val) and not pd.isna(signal_val):
344
+ if macd_val > signal_val: macd_signal = "BULLISH_CROSS"
345
+ elif macd_val < signal_val: macd_signal = "BEARISH_CROSS"
346
+
347
+ trend_signal = "NEUTRAL"
348
+ sma50 = current_data.get('SMA_50')
349
+ sma20 = current_data.get('SMA_20')
350
+
351
+ # Check all components for trend signal
352
+ if sma200 is not None and sma50 is not None and sma20 is not None and not pd.isna(sma50) and not pd.isna(sma20):
353
+ if price > sma20 > sma50 > sma200: trend_signal = "STRONG_BULLISH"
354
+ elif price < sma20 < sma50 < sma200: trend_signal = "STRONG_BEARISH"
355
+ elif price > sma200: trend_signal = "BULLISH"
356
+ elif price < sma200: trend_signal = "BEARISH"
357
+
358
+ return {
359
+ "ticker": symbol,
360
+ "current_price": round(price, 2),
361
+ "sma_20": round(current_data['SMA_20'], 2) if not pd.isna(current_data.get('SMA_20')) else "N/A",
362
+ "ema_20": round(current_data['EMA_20'], 2) if not pd.isna(current_data.get('EMA_20')) else "N/A",
363
+ "sma_50": round(current_data['SMA_50'], 2) if not pd.isna(current_data.get('SMA_50')) else "N/A",
364
+ "sma_200": round(sma200, 2) if (sma200 is not None and not pd.isna(sma200)) else "N/A",
365
+ "rsi_14": round(rsi_val, 2) if (rsi_val is not None and not pd.isna(rsi_val)) else "N/A",
366
+ "macd": round(macd_val, 2) if (macd_val is not None and not pd.isna(macd_val)) else "N/A",
367
+ "macd_signal": macd_signal,
368
+ "rsi_signal": rsi_signal,
369
+ "trend_signal": trend_signal
370
+ }
371
+
372
+ except Exception as e:
373
+ return {"error": str(e)}
374
+
375
+ def get_fundamental_data(symbol: str) -> dict:
376
+ """
377
+ Fetches fundamental financial metrics: P/E, PEG, Debt/Equity, Debt/Asset, and Net Profit Margin.
378
+ """
379
+ print(f"[DEBUG] get_fundamental_data called for: {symbol}")
380
+ try:
381
+ symbol = check_and_fix_ticker(symbol)
382
+ ticker = yf.Ticker(symbol)
383
+ info = ticker.info
384
+
385
+ # Use info.get to handle missing keys gracefully
386
+ pe_ratio = info.get('forwardPE') or info.get('trailingPE')
387
+ peg_ratio = info.get('pegRatio')
388
+ debt_to_equity = info.get('debtToEquity')
389
+ profit_margin = info.get('profitMargins')
390
+
391
+ eps_trailing = info.get('trailingEps')
392
+ eps_forward = info.get('forwardEps')
393
+ dividend_yield = info.get('dividendYield')
394
+
395
+ # Ticker Calendar (Earnings)
396
+ next_earnings = "N/A"
397
+ try:
398
+ cal = ticker.calendar
399
+ if cal and 'Earnings Date' in cal:
400
+ dates = cal['Earnings Date']
401
+ if dates:
402
+ next_earnings = dates[0].strftime("%Y-%m-%d")
403
+ except Exception:
404
+ pass
405
+
406
+ return {
407
+ "ticker": symbol,
408
+ "pe_ratio": round(pe_ratio, 2) if pe_ratio else "N/A",
409
+ "peg_ratio": round(peg_ratio, 2) if peg_ratio else "N/A",
410
+ "eps_trailing": round(eps_trailing, 2) if eps_trailing else "N/A",
411
+ "eps_forward": round(eps_forward, 2) if eps_forward else "N/A",
412
+ "debt_to_equity": round(debt_to_equity, 2) if debt_to_equity else "N/A",
413
+ "net_profit_margin": f"{round(profit_margin * 100, 2)}%" if profit_margin else "N/A",
414
+ "dividend_yield": f"{round(dividend_yield * 100, 2)}%" if dividend_yield else "N/A",
415
+ "market_cap": info.get('marketCap', "N/A"),
416
+ "sector": info.get('sector', "N/A"),
417
+ "next_earnings_date": next_earnings,
418
+ "valuation_score": "UNDERVALUED" if (pe_ratio and pe_ratio < 15) else "PREMIUM" if (pe_ratio and pe_ratio > 30) else "FAIR_VALUE",
419
+ "quality_score": "HIGH_QUALITY" if (profit_margin and profit_margin > 0.20) else "LOW_MARGIN" if (profit_margin and profit_margin < 0.10) else "AVERAGE"
420
+ }
421
+ except Exception as e:
422
+ return {"error": str(e)}
423
+
424
+ def get_analyst_consensus(symbol: str) -> dict:
425
+ """
426
+ Fetches Wall St. analyst recommendations and price targets.
427
+ """
428
+ print(f"[DEBUG] get_analyst_consensus called for: {symbol}")
429
+ try:
430
+ symbol = check_and_fix_ticker(symbol)
431
+ ticker = yf.Ticker(symbol)
432
+ info = ticker.info
433
+
434
+ consensus = info.get('recommendationKey', 'none')
435
+ target = info.get('targetMeanPrice')
436
+ num_analysts = info.get('numberOfAnalystOpinions')
437
+
438
+ # Current price for comparison
439
+ current = info.get('currentPrice') or info.get('regularMarketPrice') or info.get('previousClose')
440
+
441
+ upside = "N/A"
442
+ if target and current and current > 0:
443
+ upside_pct = ((target - current) / current) * 100
444
+ upside = f"{round(upside_pct, 1)}%"
445
+
446
+ return {
447
+ "consensus": consensus.replace('_', ' ').title(),
448
+ "target_price": target or "N/A",
449
+ "current_price": current,
450
+ "upside_potential": upside,
451
+ "analyst_count": num_analysts or "N/A"
452
+ }
453
+ except Exception as e:
454
+ return {"error": f"Failed to fetch analyst data: {str(e)}"}
src/agora/backend/tools/news_data.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ddgs import DDGS
2
+ from transformers import pipeline
3
+ import torch
4
+ from curl_cffi import requests
5
+ from bs4 import BeautifulSoup
6
+ import yfinance as yf
7
+ import datetime
8
+ import time
9
+ from typing import Optional
10
+ from pydantic import BaseModel, Field
11
+ import concurrent.futures
12
+
13
+ # Global variable for lazy loading
14
+ _sentiment_pipeline = None
15
+
16
+ def get_sentiment_pipeline():
17
+ """
18
+ Lazy loads the FinBERT pipeline.
19
+ """
20
+ print(f"[DEBUG] get_sentiment_pipeline called")
21
+ global _sentiment_pipeline
22
+ if _sentiment_pipeline is None:
23
+ try:
24
+ print("Loading FinBERT model...")
25
+ # Use CPU to avoid CUDA complexity if not needed, or let torch decide if fast
26
+ _sentiment_pipeline = pipeline("sentiment-analysis", model="ProsusAI/finbert")
27
+ except Exception as e:
28
+ print(f"Failed to load FinBERT: {e}")
29
+ return None
30
+ return _sentiment_pipeline
31
+
32
+ def _fetch_page_content(url: str, timeout: int = 15) -> Optional[str]:
33
+ """Fetch and extract text content from a web page."""
34
+ print(f"[DEBUG] Fetching: {url}")
35
+ start_time = time.time()
36
+ try:
37
+ # Use curl_cffi to impersonate Chrome 110 (Bypasses TLS Fingerprinting)
38
+ # Headers are auto-managed by impersonate
39
+ response = requests.get(url, timeout=timeout, impersonate="chrome110")
40
+ response.raise_for_status()
41
+
42
+ soup = BeautifulSoup(response.content, 'html.parser')
43
+
44
+ # Remove ads, popups, and non-content elements
45
+ # Targeted classes: .ad, .popup, .modal, .cookie-banner, etc.
46
+ for tag in soup.select("script, style, nav, footer, header, aside, form, iframe, .ad, .popup, .modal, .cookie-banner, [id*='popup'], [class*='popup'], [class*='ad-'], [class*='banner']"):
47
+ tag.decompose()
48
+
49
+ # Extract text
50
+ text = soup.get_text(separator=' ', strip=True)
51
+ duration = round(time.time() - start_time, 2)
52
+ print(f"[DEBUG] Fetch success ({duration}s): {url}")
53
+ return text
54
+ except Exception as e:
55
+ msg = str(e)
56
+ if "403" in msg:
57
+ print(f"[INFO] Access denied (403) for {url}. Falling back to snippet.")
58
+ else:
59
+ print(f"[WARNING] Failed to fetch content from {url}: {msg}")
60
+ return None
61
+
62
+ # Validation Model
63
+ class NewsArticle(BaseModel):
64
+ title: str = Field(..., description="The headline of the news article.")
65
+ link: str = Field(..., description="The direct URL to the full article.")
66
+ snippet: str = Field(..., description="A brief summary or body text.")
67
+ datetime: Optional[str] = Field(None, description="Publication date if available.")
68
+
69
+ def search_news(ticker: str) -> str:
70
+ """
71
+ Searches for the latest news regarding the stock ticker using DuckDuckGo.
72
+ Analyzes each headline with FinBERT for sentiment.
73
+ Returns a summary string of the top 5 results with sentiment scores.
74
+ """
75
+ print(f"[DEBUG] search_news called with: {ticker}")
76
+ try:
77
+ if not ticker:
78
+ return "No ticker provided for news search."
79
+
80
+ ticker = ticker.upper().strip()
81
+ articles_pool = []
82
+
83
+ # 1. Fetch from Yahoo Finance API (Reliable)
84
+ try:
85
+ print("[DEBUG] Fetching YF API news...")
86
+ yf_ticker = yf.Ticker(ticker)
87
+ yf_raw = yf_ticker.news
88
+ if yf_raw:
89
+ for item in yf_raw:
90
+ ts = item.get('providerPublishTime')
91
+ date_str = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d') if ts else 'Unknown'
92
+ articles_pool.append(NewsArticle(
93
+ title=item.get('title', 'No Title'),
94
+ link=item.get('link', ''),
95
+ snippet=f"Source: {item.get('publisher')} - {date_str}",
96
+ datetime=date_str
97
+ ))
98
+ except Exception as e:
99
+ print(f"[WARNING] YF API failed: {e}")
100
+
101
+ # 2. Add DuckDuckGo Targeted Search (Secondary)
102
+ # Targeted sites: CNBC, Bloomberg, Investing.com, MarketWatch
103
+ query = f"{ticker} stock news (site:cnbc.com OR site:bloomberg.com OR site:investing.com OR site:marketwatch.com)"
104
+
105
+ with DDGS() as ddgs:
106
+ raw_results = list(ddgs.news(query, max_results=10))
107
+ for raw_item in raw_results:
108
+ try:
109
+ articles_pool.append(NewsArticle(
110
+ title=raw_item.get('title', 'No Title'),
111
+ link=raw_item.get('url', ''),
112
+ snippet=raw_item.get('body', ''),
113
+ datetime=raw_item.get('date', 'Unknown')
114
+ ))
115
+ except: continue
116
+
117
+ if not articles_pool:
118
+ return f"No recent news found for {ticker}."
119
+
120
+ # Deduplicate by Title
121
+ seen_titles = set()
122
+ unique_articles = []
123
+ for a in articles_pool:
124
+ if a.title not in seen_titles:
125
+ seen_titles.add(a.title)
126
+ unique_articles.append(a)
127
+
128
+ # Analyze Top 5
129
+ top_articles = unique_articles[:5]
130
+ sentiment_pipe = get_sentiment_pipeline()
131
+
132
+ results = []
133
+
134
+ # Helper to process
135
+ def process_article(article):
136
+ # FinBERT Analysis
137
+ sentiment_tag = ""
138
+ if sentiment_pipe:
139
+ try:
140
+ # Prefer full text fetch, fallback to Snippet
141
+ content = article.snippet if article.snippet else article.title
142
+ analysis_type = "Snippet" if article.snippet else "Headline"
143
+
144
+
145
+ # Try fetch full text with improved headers
146
+ if article.link:
147
+ full_text = _fetch_page_content(article.link)
148
+ if full_text and len(full_text) > 100:
149
+ content = full_text
150
+ analysis_type = "Full Text"
151
+ else:
152
+ # Fallback log
153
+ print(f"[DEBUG] Content too short/failed for {article.title[:30]}... using Snippet.")
154
+
155
+ # Truncate for BERT
156
+ score = sentiment_pipe(content[:2000])[0]
157
+ label = score['label']
158
+ conf = round(score['score'], 2)
159
+ sentiment_tag = f" [FinBERT ({analysis_type}): {label} ({conf})]"
160
+ except Exception as e:
161
+ sentiment_tag = f" [FinBERT: Error]"
162
+
163
+ return f"- [{article.datetime}] {article.title}{sentiment_tag}"
164
+
165
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
166
+ results = list(executor.map(process_article, top_articles))
167
+
168
+ return f"Recent News for {ticker} (Sources: YF, CNBC, Bloomberg, Investing):\n" + "\n".join(results)
169
+
170
+ except Exception as e:
171
+ return f"Error fetching news for {ticker}: {str(e)}"
src/agora/backend/tools/pl_calculator.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import List, Dict, Any
3
+
4
+ def calculate_strategy_metrics(legs: List[Dict[str, Any]], spot_price: float) -> Dict[str, Any]:
5
+ """
6
+ Calculates Max Profit, Max Loss, and Breakeven for a given set of option legs.
7
+ Leg format: {'action': 'BUY'/'SELL', 'type': 'CALL'/'PUT', 'strike': float, 'price': float, 'expiry': str}
8
+ """
9
+ if not legs:
10
+ return {"max_profit": 0, "max_loss": 0, "breakeven": 0, "net_cost": 0}
11
+
12
+ # 1. Calculate Net Debit/Credit
13
+ net_premium = 0
14
+ for leg in legs:
15
+ multiplier = 1 if leg['action'].upper() == 'BUY' else -1
16
+ net_premium += leg['price'] * multiplier
17
+
18
+ # Positive net_premium = Debit (Paying)
19
+ # Negative net_premium = Credit (Receiving)
20
+ is_debit = net_premium > 0
21
+ net_cost = abs(net_premium) * 100 # Multiplied by contract size
22
+
23
+ # 2. Identify Strategy Type and Calculate Risk
24
+ leg_count = len(legs)
25
+ expiries = set(leg['expiry'] for leg in legs)
26
+ is_multi_expiry = len(expiries) > 1
27
+
28
+ # Sort legs by strike for easier analysis
29
+ sorted_legs = sorted(legs, key=lambda x: x['strike'])
30
+
31
+ max_profit = 0
32
+ max_loss = 0
33
+
34
+ if is_multi_expiry:
35
+ # Complex calculation for Calendars/Diagonals
36
+ # For simplicity in this version, we provide an ESTIMATE based on premium paid
37
+ # Usually Max Loss = Net Debit Paid
38
+ if is_debit:
39
+ max_loss = net_cost
40
+ # Max profit is capped by the back-month value at front-month expiration
41
+ # This is hard to calculate without a model, so we flag it as an estimate
42
+ max_profit = "Estimated (Limited)"
43
+ else:
44
+ # Net Credit Calendar (Rare/Risky)
45
+ max_loss = "Unlimited"
46
+ max_profit = net_cost
47
+
48
+ elif leg_count == 1:
49
+ # Long/Short Call/Put
50
+ if legs[0]['action'].upper() == 'BUY':
51
+ max_loss = net_cost
52
+ max_profit = "Unlimited"
53
+ else:
54
+ max_profit = net_cost
55
+ max_loss = "Unlimited"
56
+
57
+ elif leg_count == 2:
58
+ # Spreads (Vertical)
59
+ s1, s2 = sorted_legs[0]['strike'], sorted_legs[1]['strike']
60
+ spread_width = (s2 - s1) * 100
61
+
62
+ if is_debit:
63
+ max_loss = net_cost
64
+ max_profit = spread_width - net_cost
65
+ else:
66
+ max_profit = net_cost
67
+ max_loss = spread_width - net_cost
68
+
69
+ elif leg_count == 4:
70
+ # Iron Condor / Iron Butterfly
71
+ # Max Profit = Net Credit
72
+ # Max Loss = Width of widest wing - Net Credit
73
+ if not is_debit:
74
+ put_spread_width = (sorted_legs[1]['strike'] - sorted_legs[0]['strike']) * 100
75
+ call_spread_width = (sorted_legs[3]['strike'] - sorted_legs[2]['strike']) * 100
76
+ widest_wing = max(put_spread_width, call_spread_width)
77
+ max_profit = net_cost
78
+ max_loss = widest_wing - net_cost
79
+ else:
80
+ # Reverse Iron Condor (Debit)
81
+ max_loss = net_cost
82
+ max_profit = max(sorted_legs[1]['strike'] - sorted_legs[0]['strike'], sorted_legs[3]['strike'] - sorted_legs[2]['strike']) * 100 - net_cost
83
+
84
+ elif leg_count == 3:
85
+ # Butterfly / Christmas Tree
86
+ # S1 (Buy 1), S2 (Sell 2), S3 (Buy 1)
87
+ if is_debit:
88
+ wing_width = (sorted_legs[1]['strike'] - sorted_legs[0]['strike']) * 100
89
+ max_loss = net_cost
90
+ max_profit = wing_width - net_cost
91
+
92
+ return {
93
+ "max_profit": max_profit,
94
+ "max_loss": max_loss,
95
+ "net_premium": round(net_premium, 2),
96
+ "is_debit": is_debit,
97
+ "leg_details": [f"{l['action']} {l['type']} {l['strike']} @ {l['price']} (Exp: {l['expiry']})" for l in legs]
98
+ }
src/agora/backend/utility/__init__.py ADDED
File without changes
src/agora/backend/utility/autogen_model_factory.py ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from azure.identity import DefaultAzureCredential, get_bearer_token_provider
3
+
4
+ class AutoGenModelFactory:
5
+ """
6
+ Factory for creating AutoGen compatible model instances.
7
+ """
8
+
9
+ @staticmethod
10
+ def get_model(provider: str = "azure", # azure, openai, google, groq, ollama
11
+ model_name: str = "gpt-4o",
12
+ temperature: float = 0,
13
+ model_info: dict = None
14
+ ):
15
+ """
16
+ Returns an AutoGen OpenAIChatCompletionClient instance.
17
+ """
18
+
19
+ # Lazy import to avoid dependency issues if autogen is not installed
20
+ try:
21
+ from autogen_ext.models.openai import OpenAIChatCompletionClient
22
+ except ImportError as e:
23
+ raise ImportError("AutoGen libraries (autogen-agentchat, autogen-ext[openai]) are not installed.") from e
24
+
25
+ # ----------------------------------------------------------------------
26
+ # AZURE
27
+ # ----------------------------------------------------------------------
28
+ if provider.lower() == "azure":
29
+ token_provider = get_bearer_token_provider(
30
+ DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
31
+ )
32
+ return OpenAIChatCompletionClient(
33
+ model=model_name,
34
+ azure_endpoint=os.environ["AZURE_OPENAI_API_URI"],
35
+ api_version=os.environ["AZURE_OPENAI_API_VERSION"],
36
+ azure_ad_token_provider=token_provider,
37
+ temperature=temperature,
38
+ )
39
+
40
+ # ----------------------------------------------------------------------
41
+ # OPENAI
42
+ # ----------------------------------------------------------------------
43
+ elif provider.lower() == "openai":
44
+ return OpenAIChatCompletionClient(
45
+ model=model_name,
46
+ api_key=os.environ["OPENAI_API_KEY"],
47
+ temperature=temperature,
48
+ )
49
+
50
+ # ----------------------------------------------------------------------
51
+ # GOOGLE (GEMINI) via OpenAI Compat
52
+ # ----------------------------------------------------------------------
53
+ elif provider.lower() == "google" or provider.lower() == "gemini":
54
+ if model_info is None:
55
+ model_info = {
56
+ "family": "gemini",
57
+ "vision": False,
58
+ "function_calling": True,
59
+ "json_output": True,
60
+ "structured_output": False
61
+ }
62
+
63
+ return OpenAIChatCompletionClient(
64
+ model=model_name,
65
+ base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
66
+ api_key=os.environ["GOOGLE_API_KEY"],
67
+ model_info=model_info,
68
+ temperature=temperature,
69
+ max_tokens=2048,
70
+ structured_output=False, # Disable for Gemini compatibility
71
+ extra_headers={"x-goog-api-key": os.environ["GOOGLE_API_KEY"]}
72
+ )
73
+
74
+ # ----------------------------------------------------------------------
75
+ # GROQ
76
+ # ----------------------------------------------------------------------
77
+ elif provider.lower() == "groq":
78
+ if model_info is None:
79
+ model_info = {
80
+ "family": "llama", # Use llama family for Groq
81
+ "vision": False,
82
+ "function_calling": True,
83
+ "json_output": True,
84
+ "structured_output": False
85
+ }
86
+
87
+ return OpenAIChatCompletionClient(
88
+ model=model_name,
89
+ base_url="https://api.groq.com/openai/v1",
90
+ api_key=os.environ["GROQ_API_KEY"],
91
+ model_info=model_info,
92
+ temperature=temperature,
93
+ structured_output=False, # Disable for Groq compatibility
94
+ max_tokens=2048
95
+ )
96
+
97
+ # ----------------------------------------------------------------------
98
+ # OLLAMA
99
+ # ----------------------------------------------------------------------
100
+ elif provider.lower() == "ollama":
101
+ # Ensure model_info defaults to empty dict if None
102
+ info = model_info if model_info is not None else {}
103
+ return OpenAIChatCompletionClient(
104
+ model=model_name,
105
+ base_url="http://localhost:11434/v1",
106
+ api_key="ollama", # dummy key
107
+ model_info=info,
108
+ temperature=temperature,
109
+ )
110
+
111
+ else:
112
+ raise ValueError(f"Unsupported AutoGen provider: {provider}")
src/agora/frontend/.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
src/agora/frontend/.vscode/extensions.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "recommendations": ["Vue.volar"]
3
+ }
src/agora/frontend/README.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Vue 3 + Vite
2
+
3
+ This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
4
+
5
+ Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
src/agora/frontend/index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Agora - Multi-Agent Trading Intelligence</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.js"></script>
12
+ </body>
13
+ </html>
src/agora/frontend/package-lock.json ADDED
@@ -0,0 +1,1627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "version": "0.0.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "frontend",
9
+ "version": "0.0.0",
10
+ "dependencies": {
11
+ "axios": "^1.16.0",
12
+ "lucide-vue-next": "^0.563.0",
13
+ "vue": "^3.5.24"
14
+ },
15
+ "devDependencies": {
16
+ "@vitejs/plugin-vue": "^6.0.1",
17
+ "vite": "^7.3.2"
18
+ }
19
+ },
20
+ "node_modules/@babel/helper-string-parser": {
21
+ "version": "7.27.1",
22
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
23
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
24
+ "license": "MIT",
25
+ "engines": {
26
+ "node": ">=6.9.0"
27
+ }
28
+ },
29
+ "node_modules/@babel/helper-validator-identifier": {
30
+ "version": "7.28.5",
31
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
32
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
33
+ "license": "MIT",
34
+ "engines": {
35
+ "node": ">=6.9.0"
36
+ }
37
+ },
38
+ "node_modules/@babel/parser": {
39
+ "version": "7.29.0",
40
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
41
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
42
+ "license": "MIT",
43
+ "dependencies": {
44
+ "@babel/types": "^7.29.0"
45
+ },
46
+ "bin": {
47
+ "parser": "bin/babel-parser.js"
48
+ },
49
+ "engines": {
50
+ "node": ">=6.0.0"
51
+ }
52
+ },
53
+ "node_modules/@babel/types": {
54
+ "version": "7.29.0",
55
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
56
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
57
+ "license": "MIT",
58
+ "dependencies": {
59
+ "@babel/helper-string-parser": "^7.27.1",
60
+ "@babel/helper-validator-identifier": "^7.28.5"
61
+ },
62
+ "engines": {
63
+ "node": ">=6.9.0"
64
+ }
65
+ },
66
+ "node_modules/@esbuild/aix-ppc64": {
67
+ "version": "0.27.2",
68
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
69
+ "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
70
+ "cpu": [
71
+ "ppc64"
72
+ ],
73
+ "dev": true,
74
+ "license": "MIT",
75
+ "optional": true,
76
+ "os": [
77
+ "aix"
78
+ ],
79
+ "engines": {
80
+ "node": ">=18"
81
+ }
82
+ },
83
+ "node_modules/@esbuild/android-arm": {
84
+ "version": "0.27.2",
85
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
86
+ "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
87
+ "cpu": [
88
+ "arm"
89
+ ],
90
+ "dev": true,
91
+ "license": "MIT",
92
+ "optional": true,
93
+ "os": [
94
+ "android"
95
+ ],
96
+ "engines": {
97
+ "node": ">=18"
98
+ }
99
+ },
100
+ "node_modules/@esbuild/android-arm64": {
101
+ "version": "0.27.2",
102
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
103
+ "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
104
+ "cpu": [
105
+ "arm64"
106
+ ],
107
+ "dev": true,
108
+ "license": "MIT",
109
+ "optional": true,
110
+ "os": [
111
+ "android"
112
+ ],
113
+ "engines": {
114
+ "node": ">=18"
115
+ }
116
+ },
117
+ "node_modules/@esbuild/android-x64": {
118
+ "version": "0.27.2",
119
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
120
+ "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
121
+ "cpu": [
122
+ "x64"
123
+ ],
124
+ "dev": true,
125
+ "license": "MIT",
126
+ "optional": true,
127
+ "os": [
128
+ "android"
129
+ ],
130
+ "engines": {
131
+ "node": ">=18"
132
+ }
133
+ },
134
+ "node_modules/@esbuild/darwin-arm64": {
135
+ "version": "0.27.2",
136
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
137
+ "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
138
+ "cpu": [
139
+ "arm64"
140
+ ],
141
+ "dev": true,
142
+ "license": "MIT",
143
+ "optional": true,
144
+ "os": [
145
+ "darwin"
146
+ ],
147
+ "engines": {
148
+ "node": ">=18"
149
+ }
150
+ },
151
+ "node_modules/@esbuild/darwin-x64": {
152
+ "version": "0.27.2",
153
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
154
+ "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
155
+ "cpu": [
156
+ "x64"
157
+ ],
158
+ "dev": true,
159
+ "license": "MIT",
160
+ "optional": true,
161
+ "os": [
162
+ "darwin"
163
+ ],
164
+ "engines": {
165
+ "node": ">=18"
166
+ }
167
+ },
168
+ "node_modules/@esbuild/freebsd-arm64": {
169
+ "version": "0.27.2",
170
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
171
+ "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
172
+ "cpu": [
173
+ "arm64"
174
+ ],
175
+ "dev": true,
176
+ "license": "MIT",
177
+ "optional": true,
178
+ "os": [
179
+ "freebsd"
180
+ ],
181
+ "engines": {
182
+ "node": ">=18"
183
+ }
184
+ },
185
+ "node_modules/@esbuild/freebsd-x64": {
186
+ "version": "0.27.2",
187
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
188
+ "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
189
+ "cpu": [
190
+ "x64"
191
+ ],
192
+ "dev": true,
193
+ "license": "MIT",
194
+ "optional": true,
195
+ "os": [
196
+ "freebsd"
197
+ ],
198
+ "engines": {
199
+ "node": ">=18"
200
+ }
201
+ },
202
+ "node_modules/@esbuild/linux-arm": {
203
+ "version": "0.27.2",
204
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
205
+ "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
206
+ "cpu": [
207
+ "arm"
208
+ ],
209
+ "dev": true,
210
+ "license": "MIT",
211
+ "optional": true,
212
+ "os": [
213
+ "linux"
214
+ ],
215
+ "engines": {
216
+ "node": ">=18"
217
+ }
218
+ },
219
+ "node_modules/@esbuild/linux-arm64": {
220
+ "version": "0.27.2",
221
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
222
+ "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
223
+ "cpu": [
224
+ "arm64"
225
+ ],
226
+ "dev": true,
227
+ "license": "MIT",
228
+ "optional": true,
229
+ "os": [
230
+ "linux"
231
+ ],
232
+ "engines": {
233
+ "node": ">=18"
234
+ }
235
+ },
236
+ "node_modules/@esbuild/linux-ia32": {
237
+ "version": "0.27.2",
238
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
239
+ "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
240
+ "cpu": [
241
+ "ia32"
242
+ ],
243
+ "dev": true,
244
+ "license": "MIT",
245
+ "optional": true,
246
+ "os": [
247
+ "linux"
248
+ ],
249
+ "engines": {
250
+ "node": ">=18"
251
+ }
252
+ },
253
+ "node_modules/@esbuild/linux-loong64": {
254
+ "version": "0.27.2",
255
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
256
+ "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
257
+ "cpu": [
258
+ "loong64"
259
+ ],
260
+ "dev": true,
261
+ "license": "MIT",
262
+ "optional": true,
263
+ "os": [
264
+ "linux"
265
+ ],
266
+ "engines": {
267
+ "node": ">=18"
268
+ }
269
+ },
270
+ "node_modules/@esbuild/linux-mips64el": {
271
+ "version": "0.27.2",
272
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
273
+ "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
274
+ "cpu": [
275
+ "mips64el"
276
+ ],
277
+ "dev": true,
278
+ "license": "MIT",
279
+ "optional": true,
280
+ "os": [
281
+ "linux"
282
+ ],
283
+ "engines": {
284
+ "node": ">=18"
285
+ }
286
+ },
287
+ "node_modules/@esbuild/linux-ppc64": {
288
+ "version": "0.27.2",
289
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
290
+ "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
291
+ "cpu": [
292
+ "ppc64"
293
+ ],
294
+ "dev": true,
295
+ "license": "MIT",
296
+ "optional": true,
297
+ "os": [
298
+ "linux"
299
+ ],
300
+ "engines": {
301
+ "node": ">=18"
302
+ }
303
+ },
304
+ "node_modules/@esbuild/linux-riscv64": {
305
+ "version": "0.27.2",
306
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
307
+ "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
308
+ "cpu": [
309
+ "riscv64"
310
+ ],
311
+ "dev": true,
312
+ "license": "MIT",
313
+ "optional": true,
314
+ "os": [
315
+ "linux"
316
+ ],
317
+ "engines": {
318
+ "node": ">=18"
319
+ }
320
+ },
321
+ "node_modules/@esbuild/linux-s390x": {
322
+ "version": "0.27.2",
323
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
324
+ "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
325
+ "cpu": [
326
+ "s390x"
327
+ ],
328
+ "dev": true,
329
+ "license": "MIT",
330
+ "optional": true,
331
+ "os": [
332
+ "linux"
333
+ ],
334
+ "engines": {
335
+ "node": ">=18"
336
+ }
337
+ },
338
+ "node_modules/@esbuild/linux-x64": {
339
+ "version": "0.27.2",
340
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
341
+ "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
342
+ "cpu": [
343
+ "x64"
344
+ ],
345
+ "dev": true,
346
+ "license": "MIT",
347
+ "optional": true,
348
+ "os": [
349
+ "linux"
350
+ ],
351
+ "engines": {
352
+ "node": ">=18"
353
+ }
354
+ },
355
+ "node_modules/@esbuild/netbsd-arm64": {
356
+ "version": "0.27.2",
357
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
358
+ "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
359
+ "cpu": [
360
+ "arm64"
361
+ ],
362
+ "dev": true,
363
+ "license": "MIT",
364
+ "optional": true,
365
+ "os": [
366
+ "netbsd"
367
+ ],
368
+ "engines": {
369
+ "node": ">=18"
370
+ }
371
+ },
372
+ "node_modules/@esbuild/netbsd-x64": {
373
+ "version": "0.27.2",
374
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
375
+ "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
376
+ "cpu": [
377
+ "x64"
378
+ ],
379
+ "dev": true,
380
+ "license": "MIT",
381
+ "optional": true,
382
+ "os": [
383
+ "netbsd"
384
+ ],
385
+ "engines": {
386
+ "node": ">=18"
387
+ }
388
+ },
389
+ "node_modules/@esbuild/openbsd-arm64": {
390
+ "version": "0.27.2",
391
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
392
+ "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
393
+ "cpu": [
394
+ "arm64"
395
+ ],
396
+ "dev": true,
397
+ "license": "MIT",
398
+ "optional": true,
399
+ "os": [
400
+ "openbsd"
401
+ ],
402
+ "engines": {
403
+ "node": ">=18"
404
+ }
405
+ },
406
+ "node_modules/@esbuild/openbsd-x64": {
407
+ "version": "0.27.2",
408
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
409
+ "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
410
+ "cpu": [
411
+ "x64"
412
+ ],
413
+ "dev": true,
414
+ "license": "MIT",
415
+ "optional": true,
416
+ "os": [
417
+ "openbsd"
418
+ ],
419
+ "engines": {
420
+ "node": ">=18"
421
+ }
422
+ },
423
+ "node_modules/@esbuild/openharmony-arm64": {
424
+ "version": "0.27.2",
425
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
426
+ "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
427
+ "cpu": [
428
+ "arm64"
429
+ ],
430
+ "dev": true,
431
+ "license": "MIT",
432
+ "optional": true,
433
+ "os": [
434
+ "openharmony"
435
+ ],
436
+ "engines": {
437
+ "node": ">=18"
438
+ }
439
+ },
440
+ "node_modules/@esbuild/sunos-x64": {
441
+ "version": "0.27.2",
442
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
443
+ "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
444
+ "cpu": [
445
+ "x64"
446
+ ],
447
+ "dev": true,
448
+ "license": "MIT",
449
+ "optional": true,
450
+ "os": [
451
+ "sunos"
452
+ ],
453
+ "engines": {
454
+ "node": ">=18"
455
+ }
456
+ },
457
+ "node_modules/@esbuild/win32-arm64": {
458
+ "version": "0.27.2",
459
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
460
+ "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
461
+ "cpu": [
462
+ "arm64"
463
+ ],
464
+ "dev": true,
465
+ "license": "MIT",
466
+ "optional": true,
467
+ "os": [
468
+ "win32"
469
+ ],
470
+ "engines": {
471
+ "node": ">=18"
472
+ }
473
+ },
474
+ "node_modules/@esbuild/win32-ia32": {
475
+ "version": "0.27.2",
476
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
477
+ "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
478
+ "cpu": [
479
+ "ia32"
480
+ ],
481
+ "dev": true,
482
+ "license": "MIT",
483
+ "optional": true,
484
+ "os": [
485
+ "win32"
486
+ ],
487
+ "engines": {
488
+ "node": ">=18"
489
+ }
490
+ },
491
+ "node_modules/@esbuild/win32-x64": {
492
+ "version": "0.27.2",
493
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
494
+ "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
495
+ "cpu": [
496
+ "x64"
497
+ ],
498
+ "dev": true,
499
+ "license": "MIT",
500
+ "optional": true,
501
+ "os": [
502
+ "win32"
503
+ ],
504
+ "engines": {
505
+ "node": ">=18"
506
+ }
507
+ },
508
+ "node_modules/@jridgewell/sourcemap-codec": {
509
+ "version": "1.5.5",
510
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
511
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
512
+ "license": "MIT"
513
+ },
514
+ "node_modules/@rolldown/pluginutils": {
515
+ "version": "1.0.0-rc.2",
516
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz",
517
+ "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==",
518
+ "dev": true,
519
+ "license": "MIT"
520
+ },
521
+ "node_modules/@rollup/rollup-android-arm-eabi": {
522
+ "version": "4.59.0",
523
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
524
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
525
+ "cpu": [
526
+ "arm"
527
+ ],
528
+ "dev": true,
529
+ "license": "MIT",
530
+ "optional": true,
531
+ "os": [
532
+ "android"
533
+ ]
534
+ },
535
+ "node_modules/@rollup/rollup-android-arm64": {
536
+ "version": "4.59.0",
537
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
538
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
539
+ "cpu": [
540
+ "arm64"
541
+ ],
542
+ "dev": true,
543
+ "license": "MIT",
544
+ "optional": true,
545
+ "os": [
546
+ "android"
547
+ ]
548
+ },
549
+ "node_modules/@rollup/rollup-darwin-arm64": {
550
+ "version": "4.59.0",
551
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
552
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
553
+ "cpu": [
554
+ "arm64"
555
+ ],
556
+ "dev": true,
557
+ "license": "MIT",
558
+ "optional": true,
559
+ "os": [
560
+ "darwin"
561
+ ]
562
+ },
563
+ "node_modules/@rollup/rollup-darwin-x64": {
564
+ "version": "4.59.0",
565
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
566
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
567
+ "cpu": [
568
+ "x64"
569
+ ],
570
+ "dev": true,
571
+ "license": "MIT",
572
+ "optional": true,
573
+ "os": [
574
+ "darwin"
575
+ ]
576
+ },
577
+ "node_modules/@rollup/rollup-freebsd-arm64": {
578
+ "version": "4.59.0",
579
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
580
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
581
+ "cpu": [
582
+ "arm64"
583
+ ],
584
+ "dev": true,
585
+ "license": "MIT",
586
+ "optional": true,
587
+ "os": [
588
+ "freebsd"
589
+ ]
590
+ },
591
+ "node_modules/@rollup/rollup-freebsd-x64": {
592
+ "version": "4.59.0",
593
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
594
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
595
+ "cpu": [
596
+ "x64"
597
+ ],
598
+ "dev": true,
599
+ "license": "MIT",
600
+ "optional": true,
601
+ "os": [
602
+ "freebsd"
603
+ ]
604
+ },
605
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
606
+ "version": "4.59.0",
607
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
608
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
609
+ "cpu": [
610
+ "arm"
611
+ ],
612
+ "dev": true,
613
+ "license": "MIT",
614
+ "optional": true,
615
+ "os": [
616
+ "linux"
617
+ ]
618
+ },
619
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
620
+ "version": "4.59.0",
621
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
622
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
623
+ "cpu": [
624
+ "arm"
625
+ ],
626
+ "dev": true,
627
+ "license": "MIT",
628
+ "optional": true,
629
+ "os": [
630
+ "linux"
631
+ ]
632
+ },
633
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
634
+ "version": "4.59.0",
635
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
636
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
637
+ "cpu": [
638
+ "arm64"
639
+ ],
640
+ "dev": true,
641
+ "license": "MIT",
642
+ "optional": true,
643
+ "os": [
644
+ "linux"
645
+ ]
646
+ },
647
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
648
+ "version": "4.59.0",
649
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
650
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
651
+ "cpu": [
652
+ "arm64"
653
+ ],
654
+ "dev": true,
655
+ "license": "MIT",
656
+ "optional": true,
657
+ "os": [
658
+ "linux"
659
+ ]
660
+ },
661
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
662
+ "version": "4.59.0",
663
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
664
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
665
+ "cpu": [
666
+ "loong64"
667
+ ],
668
+ "dev": true,
669
+ "license": "MIT",
670
+ "optional": true,
671
+ "os": [
672
+ "linux"
673
+ ]
674
+ },
675
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
676
+ "version": "4.59.0",
677
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
678
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
679
+ "cpu": [
680
+ "loong64"
681
+ ],
682
+ "dev": true,
683
+ "license": "MIT",
684
+ "optional": true,
685
+ "os": [
686
+ "linux"
687
+ ]
688
+ },
689
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
690
+ "version": "4.59.0",
691
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
692
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
693
+ "cpu": [
694
+ "ppc64"
695
+ ],
696
+ "dev": true,
697
+ "license": "MIT",
698
+ "optional": true,
699
+ "os": [
700
+ "linux"
701
+ ]
702
+ },
703
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
704
+ "version": "4.59.0",
705
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
706
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
707
+ "cpu": [
708
+ "ppc64"
709
+ ],
710
+ "dev": true,
711
+ "license": "MIT",
712
+ "optional": true,
713
+ "os": [
714
+ "linux"
715
+ ]
716
+ },
717
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
718
+ "version": "4.59.0",
719
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
720
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
721
+ "cpu": [
722
+ "riscv64"
723
+ ],
724
+ "dev": true,
725
+ "license": "MIT",
726
+ "optional": true,
727
+ "os": [
728
+ "linux"
729
+ ]
730
+ },
731
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
732
+ "version": "4.59.0",
733
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
734
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
735
+ "cpu": [
736
+ "riscv64"
737
+ ],
738
+ "dev": true,
739
+ "license": "MIT",
740
+ "optional": true,
741
+ "os": [
742
+ "linux"
743
+ ]
744
+ },
745
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
746
+ "version": "4.59.0",
747
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
748
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
749
+ "cpu": [
750
+ "s390x"
751
+ ],
752
+ "dev": true,
753
+ "license": "MIT",
754
+ "optional": true,
755
+ "os": [
756
+ "linux"
757
+ ]
758
+ },
759
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
760
+ "version": "4.59.0",
761
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
762
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
763
+ "cpu": [
764
+ "x64"
765
+ ],
766
+ "dev": true,
767
+ "license": "MIT",
768
+ "optional": true,
769
+ "os": [
770
+ "linux"
771
+ ]
772
+ },
773
+ "node_modules/@rollup/rollup-linux-x64-musl": {
774
+ "version": "4.59.0",
775
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
776
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
777
+ "cpu": [
778
+ "x64"
779
+ ],
780
+ "dev": true,
781
+ "license": "MIT",
782
+ "optional": true,
783
+ "os": [
784
+ "linux"
785
+ ]
786
+ },
787
+ "node_modules/@rollup/rollup-openbsd-x64": {
788
+ "version": "4.59.0",
789
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
790
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
791
+ "cpu": [
792
+ "x64"
793
+ ],
794
+ "dev": true,
795
+ "license": "MIT",
796
+ "optional": true,
797
+ "os": [
798
+ "openbsd"
799
+ ]
800
+ },
801
+ "node_modules/@rollup/rollup-openharmony-arm64": {
802
+ "version": "4.59.0",
803
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
804
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
805
+ "cpu": [
806
+ "arm64"
807
+ ],
808
+ "dev": true,
809
+ "license": "MIT",
810
+ "optional": true,
811
+ "os": [
812
+ "openharmony"
813
+ ]
814
+ },
815
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
816
+ "version": "4.59.0",
817
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
818
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
819
+ "cpu": [
820
+ "arm64"
821
+ ],
822
+ "dev": true,
823
+ "license": "MIT",
824
+ "optional": true,
825
+ "os": [
826
+ "win32"
827
+ ]
828
+ },
829
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
830
+ "version": "4.59.0",
831
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
832
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
833
+ "cpu": [
834
+ "ia32"
835
+ ],
836
+ "dev": true,
837
+ "license": "MIT",
838
+ "optional": true,
839
+ "os": [
840
+ "win32"
841
+ ]
842
+ },
843
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
844
+ "version": "4.59.0",
845
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
846
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
847
+ "cpu": [
848
+ "x64"
849
+ ],
850
+ "dev": true,
851
+ "license": "MIT",
852
+ "optional": true,
853
+ "os": [
854
+ "win32"
855
+ ]
856
+ },
857
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
858
+ "version": "4.59.0",
859
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
860
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
861
+ "cpu": [
862
+ "x64"
863
+ ],
864
+ "dev": true,
865
+ "license": "MIT",
866
+ "optional": true,
867
+ "os": [
868
+ "win32"
869
+ ]
870
+ },
871
+ "node_modules/@types/estree": {
872
+ "version": "1.0.8",
873
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
874
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
875
+ "dev": true,
876
+ "license": "MIT"
877
+ },
878
+ "node_modules/@vitejs/plugin-vue": {
879
+ "version": "6.0.4",
880
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.4.tgz",
881
+ "integrity": "sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==",
882
+ "dev": true,
883
+ "license": "MIT",
884
+ "dependencies": {
885
+ "@rolldown/pluginutils": "1.0.0-rc.2"
886
+ },
887
+ "engines": {
888
+ "node": "^20.19.0 || >=22.12.0"
889
+ },
890
+ "peerDependencies": {
891
+ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0",
892
+ "vue": "^3.2.25"
893
+ }
894
+ },
895
+ "node_modules/@vue/compiler-core": {
896
+ "version": "3.5.27",
897
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz",
898
+ "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==",
899
+ "license": "MIT",
900
+ "dependencies": {
901
+ "@babel/parser": "^7.28.5",
902
+ "@vue/shared": "3.5.27",
903
+ "entities": "^7.0.0",
904
+ "estree-walker": "^2.0.2",
905
+ "source-map-js": "^1.2.1"
906
+ }
907
+ },
908
+ "node_modules/@vue/compiler-dom": {
909
+ "version": "3.5.27",
910
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz",
911
+ "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==",
912
+ "license": "MIT",
913
+ "dependencies": {
914
+ "@vue/compiler-core": "3.5.27",
915
+ "@vue/shared": "3.5.27"
916
+ }
917
+ },
918
+ "node_modules/@vue/compiler-sfc": {
919
+ "version": "3.5.27",
920
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz",
921
+ "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==",
922
+ "license": "MIT",
923
+ "dependencies": {
924
+ "@babel/parser": "^7.28.5",
925
+ "@vue/compiler-core": "3.5.27",
926
+ "@vue/compiler-dom": "3.5.27",
927
+ "@vue/compiler-ssr": "3.5.27",
928
+ "@vue/shared": "3.5.27",
929
+ "estree-walker": "^2.0.2",
930
+ "magic-string": "^0.30.21",
931
+ "postcss": "^8.5.6",
932
+ "source-map-js": "^1.2.1"
933
+ }
934
+ },
935
+ "node_modules/@vue/compiler-ssr": {
936
+ "version": "3.5.27",
937
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz",
938
+ "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==",
939
+ "license": "MIT",
940
+ "dependencies": {
941
+ "@vue/compiler-dom": "3.5.27",
942
+ "@vue/shared": "3.5.27"
943
+ }
944
+ },
945
+ "node_modules/@vue/reactivity": {
946
+ "version": "3.5.27",
947
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz",
948
+ "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==",
949
+ "license": "MIT",
950
+ "dependencies": {
951
+ "@vue/shared": "3.5.27"
952
+ }
953
+ },
954
+ "node_modules/@vue/runtime-core": {
955
+ "version": "3.5.27",
956
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz",
957
+ "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==",
958
+ "license": "MIT",
959
+ "dependencies": {
960
+ "@vue/reactivity": "3.5.27",
961
+ "@vue/shared": "3.5.27"
962
+ }
963
+ },
964
+ "node_modules/@vue/runtime-dom": {
965
+ "version": "3.5.27",
966
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz",
967
+ "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==",
968
+ "license": "MIT",
969
+ "dependencies": {
970
+ "@vue/reactivity": "3.5.27",
971
+ "@vue/runtime-core": "3.5.27",
972
+ "@vue/shared": "3.5.27",
973
+ "csstype": "^3.2.3"
974
+ }
975
+ },
976
+ "node_modules/@vue/server-renderer": {
977
+ "version": "3.5.27",
978
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz",
979
+ "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==",
980
+ "license": "MIT",
981
+ "dependencies": {
982
+ "@vue/compiler-ssr": "3.5.27",
983
+ "@vue/shared": "3.5.27"
984
+ },
985
+ "peerDependencies": {
986
+ "vue": "3.5.27"
987
+ }
988
+ },
989
+ "node_modules/@vue/shared": {
990
+ "version": "3.5.27",
991
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz",
992
+ "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==",
993
+ "license": "MIT"
994
+ },
995
+ "node_modules/asynckit": {
996
+ "version": "0.4.0",
997
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
998
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
999
+ "license": "MIT"
1000
+ },
1001
+ "node_modules/axios": {
1002
+ "version": "1.16.0",
1003
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz",
1004
+ "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==",
1005
+ "license": "MIT",
1006
+ "dependencies": {
1007
+ "follow-redirects": "^1.16.0",
1008
+ "form-data": "^4.0.5",
1009
+ "proxy-from-env": "^2.1.0"
1010
+ }
1011
+ },
1012
+ "node_modules/call-bind-apply-helpers": {
1013
+ "version": "1.0.2",
1014
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
1015
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
1016
+ "license": "MIT",
1017
+ "dependencies": {
1018
+ "es-errors": "^1.3.0",
1019
+ "function-bind": "^1.1.2"
1020
+ },
1021
+ "engines": {
1022
+ "node": ">= 0.4"
1023
+ }
1024
+ },
1025
+ "node_modules/combined-stream": {
1026
+ "version": "1.0.8",
1027
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
1028
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
1029
+ "license": "MIT",
1030
+ "dependencies": {
1031
+ "delayed-stream": "~1.0.0"
1032
+ },
1033
+ "engines": {
1034
+ "node": ">= 0.8"
1035
+ }
1036
+ },
1037
+ "node_modules/csstype": {
1038
+ "version": "3.2.3",
1039
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1040
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1041
+ "license": "MIT"
1042
+ },
1043
+ "node_modules/delayed-stream": {
1044
+ "version": "1.0.0",
1045
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
1046
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
1047
+ "license": "MIT",
1048
+ "engines": {
1049
+ "node": ">=0.4.0"
1050
+ }
1051
+ },
1052
+ "node_modules/dunder-proto": {
1053
+ "version": "1.0.1",
1054
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
1055
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
1056
+ "license": "MIT",
1057
+ "dependencies": {
1058
+ "call-bind-apply-helpers": "^1.0.1",
1059
+ "es-errors": "^1.3.0",
1060
+ "gopd": "^1.2.0"
1061
+ },
1062
+ "engines": {
1063
+ "node": ">= 0.4"
1064
+ }
1065
+ },
1066
+ "node_modules/entities": {
1067
+ "version": "7.0.1",
1068
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
1069
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
1070
+ "license": "BSD-2-Clause",
1071
+ "engines": {
1072
+ "node": ">=0.12"
1073
+ },
1074
+ "funding": {
1075
+ "url": "https://github.com/fb55/entities?sponsor=1"
1076
+ }
1077
+ },
1078
+ "node_modules/es-define-property": {
1079
+ "version": "1.0.1",
1080
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
1081
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
1082
+ "license": "MIT",
1083
+ "engines": {
1084
+ "node": ">= 0.4"
1085
+ }
1086
+ },
1087
+ "node_modules/es-errors": {
1088
+ "version": "1.3.0",
1089
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
1090
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
1091
+ "license": "MIT",
1092
+ "engines": {
1093
+ "node": ">= 0.4"
1094
+ }
1095
+ },
1096
+ "node_modules/es-object-atoms": {
1097
+ "version": "1.1.1",
1098
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
1099
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
1100
+ "license": "MIT",
1101
+ "dependencies": {
1102
+ "es-errors": "^1.3.0"
1103
+ },
1104
+ "engines": {
1105
+ "node": ">= 0.4"
1106
+ }
1107
+ },
1108
+ "node_modules/es-set-tostringtag": {
1109
+ "version": "2.1.0",
1110
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
1111
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
1112
+ "license": "MIT",
1113
+ "dependencies": {
1114
+ "es-errors": "^1.3.0",
1115
+ "get-intrinsic": "^1.2.6",
1116
+ "has-tostringtag": "^1.0.2",
1117
+ "hasown": "^2.0.2"
1118
+ },
1119
+ "engines": {
1120
+ "node": ">= 0.4"
1121
+ }
1122
+ },
1123
+ "node_modules/esbuild": {
1124
+ "version": "0.27.2",
1125
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
1126
+ "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
1127
+ "dev": true,
1128
+ "hasInstallScript": true,
1129
+ "license": "MIT",
1130
+ "bin": {
1131
+ "esbuild": "bin/esbuild"
1132
+ },
1133
+ "engines": {
1134
+ "node": ">=18"
1135
+ },
1136
+ "optionalDependencies": {
1137
+ "@esbuild/aix-ppc64": "0.27.2",
1138
+ "@esbuild/android-arm": "0.27.2",
1139
+ "@esbuild/android-arm64": "0.27.2",
1140
+ "@esbuild/android-x64": "0.27.2",
1141
+ "@esbuild/darwin-arm64": "0.27.2",
1142
+ "@esbuild/darwin-x64": "0.27.2",
1143
+ "@esbuild/freebsd-arm64": "0.27.2",
1144
+ "@esbuild/freebsd-x64": "0.27.2",
1145
+ "@esbuild/linux-arm": "0.27.2",
1146
+ "@esbuild/linux-arm64": "0.27.2",
1147
+ "@esbuild/linux-ia32": "0.27.2",
1148
+ "@esbuild/linux-loong64": "0.27.2",
1149
+ "@esbuild/linux-mips64el": "0.27.2",
1150
+ "@esbuild/linux-ppc64": "0.27.2",
1151
+ "@esbuild/linux-riscv64": "0.27.2",
1152
+ "@esbuild/linux-s390x": "0.27.2",
1153
+ "@esbuild/linux-x64": "0.27.2",
1154
+ "@esbuild/netbsd-arm64": "0.27.2",
1155
+ "@esbuild/netbsd-x64": "0.27.2",
1156
+ "@esbuild/openbsd-arm64": "0.27.2",
1157
+ "@esbuild/openbsd-x64": "0.27.2",
1158
+ "@esbuild/openharmony-arm64": "0.27.2",
1159
+ "@esbuild/sunos-x64": "0.27.2",
1160
+ "@esbuild/win32-arm64": "0.27.2",
1161
+ "@esbuild/win32-ia32": "0.27.2",
1162
+ "@esbuild/win32-x64": "0.27.2"
1163
+ }
1164
+ },
1165
+ "node_modules/estree-walker": {
1166
+ "version": "2.0.2",
1167
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
1168
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
1169
+ "license": "MIT"
1170
+ },
1171
+ "node_modules/fdir": {
1172
+ "version": "6.5.0",
1173
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1174
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1175
+ "dev": true,
1176
+ "license": "MIT",
1177
+ "engines": {
1178
+ "node": ">=12.0.0"
1179
+ },
1180
+ "peerDependencies": {
1181
+ "picomatch": "^3 || ^4"
1182
+ },
1183
+ "peerDependenciesMeta": {
1184
+ "picomatch": {
1185
+ "optional": true
1186
+ }
1187
+ }
1188
+ },
1189
+ "node_modules/follow-redirects": {
1190
+ "version": "1.16.0",
1191
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
1192
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
1193
+ "funding": [
1194
+ {
1195
+ "type": "individual",
1196
+ "url": "https://github.com/sponsors/RubenVerborgh"
1197
+ }
1198
+ ],
1199
+ "license": "MIT",
1200
+ "engines": {
1201
+ "node": ">=4.0"
1202
+ },
1203
+ "peerDependenciesMeta": {
1204
+ "debug": {
1205
+ "optional": true
1206
+ }
1207
+ }
1208
+ },
1209
+ "node_modules/form-data": {
1210
+ "version": "4.0.5",
1211
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
1212
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
1213
+ "license": "MIT",
1214
+ "dependencies": {
1215
+ "asynckit": "^0.4.0",
1216
+ "combined-stream": "^1.0.8",
1217
+ "es-set-tostringtag": "^2.1.0",
1218
+ "hasown": "^2.0.2",
1219
+ "mime-types": "^2.1.12"
1220
+ },
1221
+ "engines": {
1222
+ "node": ">= 6"
1223
+ }
1224
+ },
1225
+ "node_modules/fsevents": {
1226
+ "version": "2.3.3",
1227
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1228
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1229
+ "dev": true,
1230
+ "hasInstallScript": true,
1231
+ "license": "MIT",
1232
+ "optional": true,
1233
+ "os": [
1234
+ "darwin"
1235
+ ],
1236
+ "engines": {
1237
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1238
+ }
1239
+ },
1240
+ "node_modules/function-bind": {
1241
+ "version": "1.1.2",
1242
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
1243
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
1244
+ "license": "MIT",
1245
+ "funding": {
1246
+ "url": "https://github.com/sponsors/ljharb"
1247
+ }
1248
+ },
1249
+ "node_modules/get-intrinsic": {
1250
+ "version": "1.3.0",
1251
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
1252
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
1253
+ "license": "MIT",
1254
+ "dependencies": {
1255
+ "call-bind-apply-helpers": "^1.0.2",
1256
+ "es-define-property": "^1.0.1",
1257
+ "es-errors": "^1.3.0",
1258
+ "es-object-atoms": "^1.1.1",
1259
+ "function-bind": "^1.1.2",
1260
+ "get-proto": "^1.0.1",
1261
+ "gopd": "^1.2.0",
1262
+ "has-symbols": "^1.1.0",
1263
+ "hasown": "^2.0.2",
1264
+ "math-intrinsics": "^1.1.0"
1265
+ },
1266
+ "engines": {
1267
+ "node": ">= 0.4"
1268
+ },
1269
+ "funding": {
1270
+ "url": "https://github.com/sponsors/ljharb"
1271
+ }
1272
+ },
1273
+ "node_modules/get-proto": {
1274
+ "version": "1.0.1",
1275
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
1276
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
1277
+ "license": "MIT",
1278
+ "dependencies": {
1279
+ "dunder-proto": "^1.0.1",
1280
+ "es-object-atoms": "^1.0.0"
1281
+ },
1282
+ "engines": {
1283
+ "node": ">= 0.4"
1284
+ }
1285
+ },
1286
+ "node_modules/gopd": {
1287
+ "version": "1.2.0",
1288
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
1289
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
1290
+ "license": "MIT",
1291
+ "engines": {
1292
+ "node": ">= 0.4"
1293
+ },
1294
+ "funding": {
1295
+ "url": "https://github.com/sponsors/ljharb"
1296
+ }
1297
+ },
1298
+ "node_modules/has-symbols": {
1299
+ "version": "1.1.0",
1300
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
1301
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
1302
+ "license": "MIT",
1303
+ "engines": {
1304
+ "node": ">= 0.4"
1305
+ },
1306
+ "funding": {
1307
+ "url": "https://github.com/sponsors/ljharb"
1308
+ }
1309
+ },
1310
+ "node_modules/has-tostringtag": {
1311
+ "version": "1.0.2",
1312
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
1313
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
1314
+ "license": "MIT",
1315
+ "dependencies": {
1316
+ "has-symbols": "^1.0.3"
1317
+ },
1318
+ "engines": {
1319
+ "node": ">= 0.4"
1320
+ },
1321
+ "funding": {
1322
+ "url": "https://github.com/sponsors/ljharb"
1323
+ }
1324
+ },
1325
+ "node_modules/hasown": {
1326
+ "version": "2.0.2",
1327
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
1328
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "function-bind": "^1.1.2"
1332
+ },
1333
+ "engines": {
1334
+ "node": ">= 0.4"
1335
+ }
1336
+ },
1337
+ "node_modules/lucide-vue-next": {
1338
+ "version": "0.563.0",
1339
+ "resolved": "https://registry.npmjs.org/lucide-vue-next/-/lucide-vue-next-0.563.0.tgz",
1340
+ "integrity": "sha512-zsE/lCKtmaa7bGfhSpN84br1K9YoQ5pCN+2oKWjQQG3Lo6ufUUKBuHSjNFI6RvUevxaajNXb8XwFUKeTXG3sIA==",
1341
+ "license": "ISC",
1342
+ "peerDependencies": {
1343
+ "vue": ">=3.0.1"
1344
+ }
1345
+ },
1346
+ "node_modules/magic-string": {
1347
+ "version": "0.30.21",
1348
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
1349
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
1350
+ "license": "MIT",
1351
+ "dependencies": {
1352
+ "@jridgewell/sourcemap-codec": "^1.5.5"
1353
+ }
1354
+ },
1355
+ "node_modules/math-intrinsics": {
1356
+ "version": "1.1.0",
1357
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
1358
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
1359
+ "license": "MIT",
1360
+ "engines": {
1361
+ "node": ">= 0.4"
1362
+ }
1363
+ },
1364
+ "node_modules/mime-db": {
1365
+ "version": "1.52.0",
1366
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1367
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1368
+ "license": "MIT",
1369
+ "engines": {
1370
+ "node": ">= 0.6"
1371
+ }
1372
+ },
1373
+ "node_modules/mime-types": {
1374
+ "version": "2.1.35",
1375
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1376
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1377
+ "license": "MIT",
1378
+ "dependencies": {
1379
+ "mime-db": "1.52.0"
1380
+ },
1381
+ "engines": {
1382
+ "node": ">= 0.6"
1383
+ }
1384
+ },
1385
+ "node_modules/nanoid": {
1386
+ "version": "3.3.12",
1387
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
1388
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
1389
+ "funding": [
1390
+ {
1391
+ "type": "github",
1392
+ "url": "https://github.com/sponsors/ai"
1393
+ }
1394
+ ],
1395
+ "license": "MIT",
1396
+ "bin": {
1397
+ "nanoid": "bin/nanoid.cjs"
1398
+ },
1399
+ "engines": {
1400
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1401
+ }
1402
+ },
1403
+ "node_modules/picocolors": {
1404
+ "version": "1.1.1",
1405
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1406
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1407
+ "license": "ISC"
1408
+ },
1409
+ "node_modules/picomatch": {
1410
+ "version": "4.0.4",
1411
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
1412
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
1413
+ "dev": true,
1414
+ "license": "MIT",
1415
+ "engines": {
1416
+ "node": ">=12"
1417
+ },
1418
+ "funding": {
1419
+ "url": "https://github.com/sponsors/jonschlinkert"
1420
+ }
1421
+ },
1422
+ "node_modules/postcss": {
1423
+ "version": "8.5.15",
1424
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
1425
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
1426
+ "funding": [
1427
+ {
1428
+ "type": "opencollective",
1429
+ "url": "https://opencollective.com/postcss/"
1430
+ },
1431
+ {
1432
+ "type": "tidelift",
1433
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1434
+ },
1435
+ {
1436
+ "type": "github",
1437
+ "url": "https://github.com/sponsors/ai"
1438
+ }
1439
+ ],
1440
+ "license": "MIT",
1441
+ "dependencies": {
1442
+ "nanoid": "^3.3.12",
1443
+ "picocolors": "^1.1.1",
1444
+ "source-map-js": "^1.2.1"
1445
+ },
1446
+ "engines": {
1447
+ "node": "^10 || ^12 || >=14"
1448
+ }
1449
+ },
1450
+ "node_modules/proxy-from-env": {
1451
+ "version": "2.1.0",
1452
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
1453
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
1454
+ "license": "MIT",
1455
+ "engines": {
1456
+ "node": ">=10"
1457
+ }
1458
+ },
1459
+ "node_modules/rollup": {
1460
+ "version": "4.59.0",
1461
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
1462
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
1463
+ "dev": true,
1464
+ "license": "MIT",
1465
+ "dependencies": {
1466
+ "@types/estree": "1.0.8"
1467
+ },
1468
+ "bin": {
1469
+ "rollup": "dist/bin/rollup"
1470
+ },
1471
+ "engines": {
1472
+ "node": ">=18.0.0",
1473
+ "npm": ">=8.0.0"
1474
+ },
1475
+ "optionalDependencies": {
1476
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
1477
+ "@rollup/rollup-android-arm64": "4.59.0",
1478
+ "@rollup/rollup-darwin-arm64": "4.59.0",
1479
+ "@rollup/rollup-darwin-x64": "4.59.0",
1480
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
1481
+ "@rollup/rollup-freebsd-x64": "4.59.0",
1482
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
1483
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
1484
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
1485
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
1486
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
1487
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
1488
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
1489
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
1490
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
1491
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
1492
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
1493
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
1494
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
1495
+ "@rollup/rollup-openbsd-x64": "4.59.0",
1496
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
1497
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
1498
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
1499
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
1500
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
1501
+ "fsevents": "~2.3.2"
1502
+ }
1503
+ },
1504
+ "node_modules/source-map-js": {
1505
+ "version": "1.2.1",
1506
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1507
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1508
+ "license": "BSD-3-Clause",
1509
+ "engines": {
1510
+ "node": ">=0.10.0"
1511
+ }
1512
+ },
1513
+ "node_modules/tinyglobby": {
1514
+ "version": "0.2.15",
1515
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
1516
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
1517
+ "dev": true,
1518
+ "license": "MIT",
1519
+ "dependencies": {
1520
+ "fdir": "^6.5.0",
1521
+ "picomatch": "^4.0.3"
1522
+ },
1523
+ "engines": {
1524
+ "node": ">=12.0.0"
1525
+ },
1526
+ "funding": {
1527
+ "url": "https://github.com/sponsors/SuperchupuDev"
1528
+ }
1529
+ },
1530
+ "node_modules/vite": {
1531
+ "version": "7.3.2",
1532
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz",
1533
+ "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==",
1534
+ "dev": true,
1535
+ "license": "MIT",
1536
+ "dependencies": {
1537
+ "esbuild": "^0.27.0",
1538
+ "fdir": "^6.5.0",
1539
+ "picomatch": "^4.0.3",
1540
+ "postcss": "^8.5.6",
1541
+ "rollup": "^4.43.0",
1542
+ "tinyglobby": "^0.2.15"
1543
+ },
1544
+ "bin": {
1545
+ "vite": "bin/vite.js"
1546
+ },
1547
+ "engines": {
1548
+ "node": "^20.19.0 || >=22.12.0"
1549
+ },
1550
+ "funding": {
1551
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1552
+ },
1553
+ "optionalDependencies": {
1554
+ "fsevents": "~2.3.3"
1555
+ },
1556
+ "peerDependencies": {
1557
+ "@types/node": "^20.19.0 || >=22.12.0",
1558
+ "jiti": ">=1.21.0",
1559
+ "less": "^4.0.0",
1560
+ "lightningcss": "^1.21.0",
1561
+ "sass": "^1.70.0",
1562
+ "sass-embedded": "^1.70.0",
1563
+ "stylus": ">=0.54.8",
1564
+ "sugarss": "^5.0.0",
1565
+ "terser": "^5.16.0",
1566
+ "tsx": "^4.8.1",
1567
+ "yaml": "^2.4.2"
1568
+ },
1569
+ "peerDependenciesMeta": {
1570
+ "@types/node": {
1571
+ "optional": true
1572
+ },
1573
+ "jiti": {
1574
+ "optional": true
1575
+ },
1576
+ "less": {
1577
+ "optional": true
1578
+ },
1579
+ "lightningcss": {
1580
+ "optional": true
1581
+ },
1582
+ "sass": {
1583
+ "optional": true
1584
+ },
1585
+ "sass-embedded": {
1586
+ "optional": true
1587
+ },
1588
+ "stylus": {
1589
+ "optional": true
1590
+ },
1591
+ "sugarss": {
1592
+ "optional": true
1593
+ },
1594
+ "terser": {
1595
+ "optional": true
1596
+ },
1597
+ "tsx": {
1598
+ "optional": true
1599
+ },
1600
+ "yaml": {
1601
+ "optional": true
1602
+ }
1603
+ }
1604
+ },
1605
+ "node_modules/vue": {
1606
+ "version": "3.5.27",
1607
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz",
1608
+ "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==",
1609
+ "license": "MIT",
1610
+ "dependencies": {
1611
+ "@vue/compiler-dom": "3.5.27",
1612
+ "@vue/compiler-sfc": "3.5.27",
1613
+ "@vue/runtime-dom": "3.5.27",
1614
+ "@vue/server-renderer": "3.5.27",
1615
+ "@vue/shared": "3.5.27"
1616
+ },
1617
+ "peerDependencies": {
1618
+ "typescript": "*"
1619
+ },
1620
+ "peerDependenciesMeta": {
1621
+ "typescript": {
1622
+ "optional": true
1623
+ }
1624
+ }
1625
+ }
1626
+ }
1627
+ }
src/agora/frontend/package.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "frontend",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "axios": "^1.16.0",
13
+ "lucide-vue-next": "^0.563.0",
14
+ "vue": "^3.5.24"
15
+ },
16
+ "devDependencies": {
17
+ "@vitejs/plugin-vue": "^6.0.1",
18
+ "vite": "^7.3.2"
19
+ }
20
+ }
src/agora/frontend/public/favicon.svg ADDED
src/agora/frontend/public/vite.svg ADDED
src/agora/frontend/src/App.vue ADDED
@@ -0,0 +1,1888 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup>
2
+ import { ref, reactive, onMounted, nextTick, computed, watch } from 'vue'
3
+ import {
4
+ Rocket,
5
+ Search,
6
+ Activity,
7
+ MessageSquare,
8
+ LayoutDashboard,
9
+ BrainCircuit,
10
+ ShieldCheck,
11
+ Landmark,
12
+ AlertTriangle,
13
+ ExternalLink,
14
+ ChevronRight,
15
+ Loader2,
16
+ TrendingUp,
17
+ XCircle,
18
+ Clock,
19
+ Calendar,
20
+ Heart,
21
+ Zap,
22
+ LineChart,
23
+ Crown,
24
+ Sparkles
25
+ } from 'lucide-vue-next'
26
+
27
+ const ticker = ref('NVDA')
28
+ const provider = ref('openai')
29
+ const isAnalyzing = ref(false)
30
+ const activeAgent = ref(null)
31
+ const logs = ref([])
32
+ const results = ref([])
33
+ const error = ref(null)
34
+ const logContainer = ref(null)
35
+
36
+ const workflowAgents = [
37
+ { id: 'TechnicalAnalyst', name: 'Technical', role: 'Analyzes Market Trend (SPY), SMAs, EMAs, RSI, and MACD for directional bias.' },
38
+ { id: 'VolatilityAnalyst', name: 'Volatility', role: 'Analyzes VIX Fear Gauge, IV vs HV, and Option Liquidity.' },
39
+ { id: 'SentimentAnalyst', name: 'Sentiment', role: 'Evaluates market sentiment from news and monitors earnings risks.' },
40
+ { id: 'FundamentalAnalyst', name: 'Fundamental', role: 'Evaluates Valuation (P/E, PEG), EPS Growth, and Financial Health vs Market Risk.' },
41
+ { id: 'StrategyAdvisor', name: 'Strategy', role: 'Formulates multi-leg option strategies with risk/reward calculation.' },
42
+ { id: 'RiskManager', name: 'Risk', role: 'Critiques strategy in 2-round debate and performs math verification.' },
43
+ { id: 'LeadOrchestrator', name: 'Orchestrator', role: 'Central Control. Performs gap analysis, cross-questions agents, and issues final approval.' }
44
+ ]
45
+
46
+ const providers = [
47
+ { id: 'openai', name: 'OpenAI GPT-4o' },
48
+ { id: 'google', name: 'Google Gemini Pro' },
49
+ { id: 'groq', name: 'Groq Llama 3.3' }
50
+ ]
51
+
52
+ const agentIcons = {
53
+ 'TeamManager': Crown,
54
+ 'TechnicalAnalyst': LineChart,
55
+ 'VolatilityAnalyst': Zap,
56
+ 'SentimentAnalyst': MessageSquare,
57
+ 'FundamentalAnalyst': Landmark,
58
+ 'StrategyAdvisor': BrainCircuit,
59
+ 'RiskManager': ShieldCheck,
60
+ 'LeadOrchestrator': Crown,
61
+ 'System': LayoutDashboard,
62
+ 'User': Search,
63
+ // Backward compatibility
64
+ 'MarketAnalyst': Activity
65
+ }
66
+
67
+ const agentColors = {
68
+ 'TeamManager': '#facc15', // Yellow/Gold
69
+ 'TechnicalAnalyst': '#3b82f6', // Blue
70
+ 'VolatilityAnalyst': '#ec4899', // Pink/Magenta
71
+ 'SentimentAnalyst': '#10b981', // Emerald
72
+ 'FundamentalAnalyst': '#f59e0b', // Amber
73
+ 'StrategyAdvisor': '#8b5cf6', // Purple
74
+ 'RiskManager': '#ef4444', // Red
75
+ 'LeadOrchestrator': '#facc15', // Gold
76
+ 'System': '#94a3b8', // Slate
77
+ 'User': '#60a5fa', // Light Blue
78
+ 'MarketAnalyst': '#3b82f6'
79
+ }
80
+
81
+ // Cookie Helpers
82
+ const setCookie = (name, value, days = 30) => {
83
+ const date = new Date();
84
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
85
+ const expires = "expires=" + date.toUTCString();
86
+ document.cookie = name + "=" + value + ";" + expires + ";path=/;SameSite=Lax";
87
+ }
88
+
89
+ const getCookie = (name) => {
90
+ const nameEQ = name + "=";
91
+ const ca = document.cookie.split(';');
92
+ for(let i=0;i < ca.length;i++) {
93
+ let c = ca[i];
94
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
95
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
96
+ }
97
+ return null;
98
+ }
99
+
100
+ const getAgentIcon = (source) => {
101
+ return agentIcons[source] || LayoutDashboard
102
+ }
103
+
104
+ const getAgentColor = (source) => {
105
+ return agentColors[source] || '#94a3b8'
106
+ }
107
+
108
+ const scrollToBottom = async () => {
109
+ await nextTick()
110
+ if (logContainer.value) {
111
+ logContainer.value.scrollTop = logContainer.value.scrollHeight
112
+ }
113
+ }
114
+
115
+ const startAnalysis = () => {
116
+ if (!ticker.value || isAnalyzing.value) return
117
+
118
+ isAnalyzing.value = true
119
+ logs.value = []
120
+ results.value = []
121
+ error.value = null
122
+
123
+ const tickers = ticker.value.split(',').map(t => t.trim().toUpperCase()).filter(t => t)
124
+
125
+ if (tickers.length === 0) {
126
+ error.value = "Please enter at least one ticker."
127
+ isAnalyzing.value = false
128
+ return
129
+ }
130
+
131
+ // Save last ticker
132
+ setCookie('last_ticker', tickers[0])
133
+
134
+ analyzeTicker(tickers[0])
135
+ }
136
+
137
+ const pendingResult = ref(null)
138
+ const currentEventSource = ref(null)
139
+ const currentAnalysisId = ref(null)
140
+
141
+ const cancelAnalysis = async () => {
142
+ if (currentAnalysisId.value) {
143
+ try {
144
+ // Send cancel request to backend
145
+ await fetch(`/cancel/${currentAnalysisId.value}`, { method: 'POST' })
146
+ } catch (e) {
147
+ console.error('Failed to cancel analysis:', e)
148
+ }
149
+ }
150
+
151
+ if (currentEventSource.value) {
152
+ currentEventSource.value.close()
153
+ currentEventSource.value = null
154
+ }
155
+
156
+ isAnalyzing.value = false
157
+ activeAgent.value = null
158
+ currentAnalysisId.value = null
159
+
160
+ logs.value.push({
161
+ id: Math.random().toString(36).substr(2, 9),
162
+ source: 'System',
163
+ content: 'Analysis cancelled by user.',
164
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
165
+ })
166
+ scrollToBottom()
167
+ }
168
+
169
+ const analyzeTicker = (symbol) => {
170
+ // Use relative path for same-origin serving (FastAPI mounting frontend)
171
+ const url = `/analyze?ticker=${symbol}&provider=${provider.value}`
172
+
173
+ const eventSource = new EventSource(url)
174
+ currentEventSource.value = eventSource
175
+ pendingResult.value = null
176
+
177
+ eventSource.onmessage = (event) => {
178
+ if (event.data === '[DONE]') {
179
+ // Delay clearing active agent so user can see the final Orchestrator state
180
+ setTimeout(() => {
181
+ activeAgent.value = null
182
+ }, 3000)
183
+
184
+ if (pendingResult.value) {
185
+ results.value.push(pendingResult.value)
186
+ saveToHistory(pendingResult.value) // Save to history
187
+ pendingResult.value = null
188
+ }
189
+ eventSource.close()
190
+ isAnalyzing.value = false
191
+ return
192
+ }
193
+
194
+ try {
195
+ const data = JSON.parse(event.data)
196
+ if (data.error) {
197
+ error.value = data.error
198
+ eventSource.close()
199
+ isAnalyzing.value = false
200
+ return
201
+ }
202
+
203
+ if (data.content || data.source) {
204
+ // Capture analysis_id from first message
205
+ if (data.analysis_id && !currentAnalysisId.value) {
206
+ currentAnalysisId.value = data.analysis_id
207
+ }
208
+
209
+ if (data.source && data.source !== 'System') {
210
+ activeAgent.value = data.source
211
+ }
212
+
213
+ logs.value.push({
214
+ id: Math.random().toString(36).substr(2, 9),
215
+ source: data.source,
216
+ content: data.content,
217
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
218
+ })
219
+ scrollToBottom()
220
+ }
221
+
222
+ // Use backend-provided structured result if available (highly recommended for Groq/Llama)
223
+ if (data.source === 'RiskManager') {
224
+ if (data.structured_result) {
225
+ console.log("Using backend-provided structured result");
226
+ pendingResult.value = {
227
+ ticker: symbol,
228
+ model: providers.find(p => p.id === provider.value)?.name || provider.value,
229
+ ...data.structured_result
230
+ }
231
+ } else {
232
+ // Fallback to local regex if structured_result is missing
233
+ const jsonMatch = data.content.match(/```json\s*([\s\S]*?)\s*```/) || data.content.match(/\{[\s\S]*"final_decision"[\s\S]*\}/)
234
+ if (jsonMatch) {
235
+ try {
236
+ const rawJson = jsonMatch[1] || jsonMatch[0]
237
+ const parsed = JSON.parse(rawJson)
238
+ pendingResult.value = {
239
+ ticker: symbol,
240
+ model: providers.find(p => p.id === provider.value)?.name || provider.value,
241
+ ...parsed
242
+ }
243
+ } catch (e) {
244
+ console.log("Failed to parse result JSON", e)
245
+ }
246
+ }
247
+ }
248
+ }
249
+ } catch (e) {
250
+ console.error("Error parsing message", e)
251
+ }
252
+ }
253
+
254
+ eventSource.onerror = (err) => {
255
+ console.error("SSE Error:", err)
256
+ error.value = "Connection to server lost. Make sure the backend is running at http://localhost:8000"
257
+ eventSource.close()
258
+ currentEventSource.value = null
259
+ isAnalyzing.value = false
260
+ }
261
+ }
262
+
263
+ const formatContent = (content) => {
264
+ if (!content) return ''
265
+ let html = content
266
+ // Headers
267
+ .replace(/^### (.*$)/gm, '<h5 class="content-h3">$1</h5>')
268
+ .replace(/^## (.*$)/gm, '<h4 class="content-h2">$1</h4>')
269
+ .replace(/^# (.*$)/gm, '<h3 class="content-h1">$1</h3>')
270
+ // Bold
271
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
272
+ // Italic
273
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
274
+ // Inline Code
275
+ .replace(/`(.*?)`/g, '<code class="inline-code">$1</code>')
276
+ // Blockquotes
277
+ .replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>')
278
+ // Bullet Lists
279
+ .replace(/^\s*[-*+]\s+(.*)$/gm, '<ul><li>$1</li></ul>')
280
+ .replace(/<\/ul>\n<ul>/g, '\n') // Combine consecutive lists
281
+ // Numbered Lists
282
+ .replace(/^\s*\d+\.\s+(.*)$/gm, '<ol><li>$1</li></ol>')
283
+ .replace(/<\/ol>\n<ol>/g, '\n') // Combine consecutive lists
284
+ // New lines to br (only after block elements or within paragraphs)
285
+ .replace(/\n/g, '<br>')
286
+
287
+ return html
288
+ }
289
+
290
+ const getDecisionColor = (decision) => {
291
+ if (!decision) return 'var(--text-secondary)'
292
+ const d = decision.toUpperCase()
293
+ if (d.includes('TRADE') || d.includes('BUY')) return 'var(--success)'
294
+ if (d.includes('WAIT') || d.includes('HOLD')) return 'var(--warning)'
295
+ return 'var(--danger)'
296
+ }
297
+
298
+ // History Management
299
+ const history = ref([])
300
+ const selectedReport = ref(null)
301
+
302
+ const openReport = (item) => {
303
+ selectedReport.value = item
304
+ }
305
+
306
+ const closeReport = () => {
307
+ selectedReport.value = null
308
+ }
309
+
310
+ const loadHistory = () => {
311
+ try {
312
+ const saved = localStorage.getItem('analysis_history')
313
+ if (saved) {
314
+ history.value = JSON.parse(saved)
315
+ }
316
+ } catch (e) {
317
+ console.error("Failed to load history", e)
318
+ }
319
+ }
320
+
321
+ const saveToHistory = (result) => {
322
+ const item = {
323
+ id: Date.now(),
324
+ timestamp: new Date().toLocaleString(),
325
+ ...result
326
+ }
327
+ // Add to beginning
328
+ history.value.unshift(item)
329
+ // Keep max 20 items
330
+ if (history.value.length > 20) {
331
+ history.value = history.value.slice(0, 20)
332
+ }
333
+
334
+ try {
335
+ localStorage.setItem('analysis_history', JSON.stringify(history.value))
336
+ } catch (e) {
337
+ console.error("Failed to save history", e)
338
+ }
339
+ }
340
+
341
+ const clearHistory = () => {
342
+ history.value = []
343
+ localStorage.removeItem('analysis_history')
344
+ }
345
+
346
+ // Persistence Watcher
347
+ watch(provider, (newVal) => {
348
+ setCookie('selected_provider', newVal);
349
+ })
350
+
351
+ // Computed Progress
352
+
353
+ onMounted(() => {
354
+ loadHistory() // Load history on startup
355
+
356
+ const savedProvider = getCookie('selected_provider');
357
+ if (savedProvider && providers.some(p => p.id === savedProvider)) {
358
+ provider.value = savedProvider;
359
+ }
360
+
361
+ const savedTicker = getCookie('last_ticker');
362
+ if (savedTicker) {
363
+ ticker.value = savedTicker;
364
+ }
365
+ })
366
+ </script>
367
+
368
+ <template>
369
+ <div class="app-wrapper">
370
+ <!-- Header Area -->
371
+ <header class="main-header glass">
372
+ <div class="logo">
373
+ <div class="logo-icon">
374
+ <TrendingUp class="icon-primary" :size="32" />
375
+ </div>
376
+ <div class="logo-text">
377
+ <h1>AGO<span>RA</span></h1>
378
+ <p>Multi-Agent Trading Intelligence</p>
379
+ </div>
380
+ </div>
381
+
382
+ <div class="controls">
383
+ <div class="input-group glass">
384
+ <Search :size="18" class="search-icon" />
385
+ <input
386
+ v-model="ticker"
387
+ placeholder="Enter Ticker (e.g. NVDA, AAPL)"
388
+ @keyup.enter="startAnalysis"
389
+ :disabled="isAnalyzing"
390
+ />
391
+ </div>
392
+
393
+ <div class="select-group glass">
394
+ <BrainCircuit :size="18" class="brain-icon" />
395
+ <select v-model="provider" :disabled="isAnalyzing">
396
+ <option v-for="p in providers" :key="p.id" :value="p.id">{{ p.name }}</option>
397
+ </select>
398
+ </div>
399
+
400
+ <button
401
+ class="btn btn-primary"
402
+ @click="startAnalysis"
403
+ :disabled="isAnalyzing || !ticker"
404
+ >
405
+ <Loader2 v-if="isAnalyzing" class="animate-spin" :size="20" />
406
+ <Rocket v-else :size="20" />
407
+ <span>{{ isAnalyzing ? 'Analyzing...' : 'Launch Analysis' }}</span>
408
+ </button>
409
+
410
+ <button
411
+ v-if="isAnalyzing"
412
+ class="btn btn-danger"
413
+ @click="cancelAnalysis"
414
+ title="Cancel Analysis"
415
+ >
416
+ <XCircle :size="20" />
417
+ <span>Cancel</span>
418
+ </button>
419
+ </div>
420
+ </header>
421
+
422
+ <main class="dashboard-content">
423
+ <div class="breadcrumb-container glass">
424
+ <div class="breadcrumb-label">
425
+ <Sparkles :size="12" />
426
+ Active Intelligence Pipeline
427
+ <span class="legend-text">(Glowing icons indicate agents in action)</span>
428
+ </div>
429
+ <div class="workflow-breadcrumb">
430
+ <div
431
+ v-for="(agent, index) in workflowAgents"
432
+ :key="agent.id"
433
+ :class="['breadcrumb-item', { active: activeAgent === agent.id }]"
434
+ :data-tooltip="agent.role"
435
+ >
436
+ <div class="item-icon-wrapper" :style="{ color: getAgentColor(agent.id) }">
437
+ <component :is="getAgentIcon(agent.id)" :size="14" />
438
+ <div v-if="activeAgent === agent.id" class="bulb" :style="{ backgroundColor: getAgentColor(agent.id) }"></div>
439
+ </div>
440
+ <span class="item-label">{{ agent.name }}</span>
441
+ <ChevronRight v-if="index < workflowAgents.length - 1" :size="14" class="separator" />
442
+ </div>
443
+ </div>
444
+ </div>
445
+
446
+ <!-- Progress Bar Removed -->
447
+
448
+ <!-- Error Alert -->
449
+ <div v-if="error" class="error-alert glass">
450
+ <XCircle :size="24" />
451
+ <p>{{ error }}</p>
452
+ <button @click="error = null"><ChevronRight :size="20" /></button>
453
+ </div>
454
+
455
+ <div class="grid-layout">
456
+ <!-- Live Analysis Stream -->
457
+ <section class="panel-logs glass">
458
+ <div class="panel-header">
459
+ <h3><Activity :size="20" /> Live Agent Stream</h3>
460
+ <div v-if="isAnalyzing" class="status-indicator">
461
+ <span class="pulse"></span> Active
462
+ </div>
463
+ </div>
464
+
465
+ <div class="log-viewport" ref="logContainer">
466
+ <div v-if="logs.length === 0 && !isAnalyzing" class="empty-state">
467
+ <LayoutDashboard :size="48" />
468
+ <p>Ready to analyze. Enter a ticker to begin.</p>
469
+ </div>
470
+
471
+ <div v-for="log in logs" :key="log.id" class="log-entry" :style="{ borderLeftColor: getAgentColor(log.source) }">
472
+ <div class="log-meta">
473
+ <component :is="getAgentIcon(log.source)" :size="16" :style="{ color: getAgentColor(log.source) }" />
474
+ <span class="source" :style="{ color: getAgentColor(log.source) }">{{ log.source }}</span>
475
+ <span class="time">{{ log.timestamp }}</span>
476
+ </div>
477
+ <div class="log-content" v-html="formatContent(log.content)"></div>
478
+ </div>
479
+ </div>
480
+ </section>
481
+
482
+ <!-- Intelligence Hub (Summary & Cards) -->
483
+ <section class="panel-results">
484
+ <div class="summary-section glass">
485
+ <div class="panel-header">
486
+ <h3><ShieldCheck :size="20" /> Executive Summary</h3>
487
+ </div>
488
+
489
+ <div class="results-list">
490
+ <div v-if="isAnalyzing" class="loading-state">
491
+ <div class="sparkles-wrapper">
492
+ <Sparkles :size="48" class="sparkle-icon" />
493
+ <div class="sparkle-particles">
494
+ <span class="particle"></span>
495
+ <span class="particle"></span>
496
+ <span class="particle"></span>
497
+ <span class="particle"></span>
498
+ </div>
499
+ </div>
500
+ <p>Agents analyzing market data...</p>
501
+ </div>
502
+ <div v-else-if="results.length === 0 && !pendingResult" class="empty-state mini">
503
+ <p>Analysis cards will appear here</p>
504
+ </div>
505
+
506
+ <TransitionGroup name="list" tag="div" class="transition-container">
507
+ <!-- Finalized Results -->
508
+ <div v-for="res in results" :key="res.ticker" class="result-card glass" :style="{ borderColor: getDecisionColor(res.final_decision) }">
509
+ <div class="card-header">
510
+ <div class="ticker-badge">{{ res.ticker }}</div>
511
+ <div class="decision-badge" :style="{ backgroundColor: getDecisionColor(res.final_decision) }">
512
+ {{ res.final_decision }}
513
+ </div>
514
+ <div v-if="res.fundamental_rating || res.rating" class="fundamental-badge" :class="res.fundamental_rating || res.rating">
515
+ <Heart :size="10" /> {{ res.fundamental_rating || res.rating }}
516
+ </div>
517
+ <div class="confidence-badge">
518
+ {{ res.confidence }}% Confidence
519
+ </div>
520
+ </div>
521
+
522
+ <div class="card-body">
523
+ <h4 class="strat-title">{{ res.strategy_type }}</h4>
524
+
525
+ <!-- Strategy Legs Table -->
526
+ <div v-if="res.legs && res.legs.length" class="legs-container glass-inset">
527
+ <div class="legs-header">Strategy Components</div>
528
+ <table class="legs-table">
529
+ <thead>
530
+ <tr>
531
+ <th>Leg</th>
532
+ <th>Type</th>
533
+ <th>Strike</th>
534
+ <th>Expiry</th>
535
+ <th>Price</th>
536
+ </tr>
537
+ </thead>
538
+ <tbody>
539
+ <tr v-for="(leg, idx) in res.legs" :key="idx">
540
+ <td>
541
+ <span class="action-badge" :class="leg.action.toLowerCase()">
542
+ {{ leg.action }}
543
+ </span>
544
+ </td>
545
+ <td>{{ leg.type }}</td>
546
+ <td><span class="strike-pill">${{ leg.strike }}</span></td>
547
+ <td class="col-expiry">{{ leg.expiry }}</td>
548
+ <td class="col-price">${{ leg.price }}</td>
549
+ </tr>
550
+ </tbody>
551
+ </table>
552
+ </div>
553
+
554
+ <div class="risk-reward-grid">
555
+ <div class="rr-item">
556
+ <span class="rr-label">Max Profit</span>
557
+ <span class="rr-val profit">${{ res.max_profit }}</span>
558
+ </div>
559
+ <div class="rr-item">
560
+ <span class="rr-label">Max Loss</span>
561
+ <span class="rr-val loss">${{ res.max_loss }}</span>
562
+ </div>
563
+ <div class="rr-item">
564
+ <span class="rr-label">Entry</span>
565
+ <span class="rr-val">${{ res.entry_price || res.estimated_entry_price }}</span>
566
+ </div>
567
+ </div>
568
+
569
+ <p class="risk-info">
570
+ <AlertTriangle v-if="res.risk_warning" :size="14" />
571
+ {{ res.risk_warning || 'No specific risk warnings identified.' }}
572
+ </p>
573
+ </div>
574
+ </div>
575
+
576
+ <!-- Pending/Finalizing State -->
577
+ <div v-if="pendingResult && isAnalyzing" class="result-card glass finalizing pulse-border">
578
+ <div class="card-header">
579
+ <div class="ticker-badge">{{ pendingResult.ticker }}</div>
580
+ <div class="status-badge">FINALIZING...</div>
581
+ </div>
582
+ <div class="card-body">
583
+ <p>Agents are confirming the strategy. Hang tight...</p>
584
+ </div>
585
+ </div>
586
+ </TransitionGroup>
587
+ </div>
588
+ </div>
589
+ </section>
590
+ </div>
591
+
592
+ <!-- History Section -->
593
+ <section v-if="history.length > 0" class="history-section glass">
594
+ <div class="panel-header">
595
+ <h3><Clock :size="20" /> Analysis History</h3>
596
+ <button @click="clearHistory" class="btn-clear">Clear History</button>
597
+ </div>
598
+
599
+ <div class="history-table-container glass-inset">
600
+ <table class="history-table">
601
+ <thead>
602
+ <tr>
603
+ <th>Date</th>
604
+ <th>Ticker</th>
605
+ <th class="mobile-hide">Model</th>
606
+ <th>Decision</th>
607
+ <th class="mobile-hide">Confidence</th>
608
+ <th class="mobile-hide">Strategy</th>
609
+ <th class="mobile-hide">Expiry</th>
610
+ <th class="mobile-hide">Max Profit</th>
611
+ <th class="mobile-hide" style="width: 50px"></th>
612
+ </tr>
613
+ </thead>
614
+ <tbody>
615
+ <tr v-for="item in history" :key="item.id" @click="openReport(item)">
616
+ <td class="col-date">{{ item.timestamp }}</td>
617
+ <td class="col-ticker"><span class="ticker-pill">{{ item.ticker }}</span></td>
618
+ <td class="col-model mobile-hide"><span class="model-badge">{{ item.model || 'Unknown' }}</span></td>
619
+ <td>
620
+ <span class="decision-pill" :style="{ color: getDecisionColor(item.final_decision), borderColor: getDecisionColor(item.final_decision) }">
621
+ {{ item.final_decision }}
622
+ </span>
623
+ </td>
624
+ <td class="mobile-hide">
625
+ <div class="confidence-bar-wrapper">
626
+ <div class="confidence-bar" :style="{ width: item.confidence + '%', backgroundColor: getDecisionColor(item.final_decision) }"></div>
627
+ <span>{{ item.confidence }}%</span>
628
+ </div>
629
+ </td>
630
+ <td class="mobile-hide">{{ item.strategy_type }}</td>
631
+ <td class="mobile-hide">{{ item.expiry_date || 'N/A' }}</td>
632
+ <td class="col-profit mobile-hide" :class="{ 'has-profit': item.max_profit > 0 }">
633
+ {{ item.max_profit ? '$' + item.max_profit : '-' }}
634
+ </td>
635
+ <td class="col-action mobile-hide"><ChevronRight :size="16" /></td>
636
+ </tr>
637
+ </tbody>
638
+ </table>
639
+ </div>
640
+ </section>
641
+ </main>
642
+
643
+ <!-- Detailed Report Modal -->
644
+ <div v-if="selectedReport" class="modal-overlay" @click.self="closeReport">
645
+ <div class="modal-content glass" :style="{ borderTopColor: getDecisionColor(selectedReport.final_decision) }">
646
+ <button class="modal-close" @click="closeReport"><XCircle :size="24" /></button>
647
+
648
+ <div class="modal-header">
649
+ <h2>{{ selectedReport.ticker }} Analysis Report</h2>
650
+ <span class="timestamp">{{ selectedReport.timestamp }}</span>
651
+ </div>
652
+
653
+ <div class="modal-badges">
654
+ <div class="decision-badge large" :style="{ backgroundColor: getDecisionColor(selectedReport.final_decision) }">
655
+ {{ selectedReport.final_decision }}
656
+ </div>
657
+ <div class="confidence-badge large">
658
+ {{ selectedReport.confidence }}% Confidence
659
+ </div>
660
+ <div class="model-badge large">
661
+ <Zap :size="14" /> {{ selectedReport.model || 'AI Model' }}
662
+ </div>
663
+ <div v-if="selectedReport.rating" class="rating-badge">
664
+ <Heart :size="16" /> {{ selectedReport.rating }}
665
+ </div>
666
+ </div>
667
+
668
+ <div class="report-section">
669
+ <h3><BrainCircuit :size="18" /> Recommendation</h3>
670
+ <p class="highlight-text">{{ selectedReport.actionable_recommendation }}</p>
671
+ </div>
672
+
673
+ <div class="metrics-grid">
674
+ <div class="metric-box">
675
+ <label>Strategy</label>
676
+ <span>{{ selectedReport.strategy_type }}</span>
677
+ </div>
678
+ <div class="metric-box">
679
+ <label>Expiry</label>
680
+ <span>{{ selectedReport.expiry_date || 'N/A' }}</span>
681
+ </div>
682
+ <div class="metric-box">
683
+ <label>Entry</label>
684
+ <span>{{ selectedReport.entry_signal }} @ ${{ selectedReport.entry_price || 'N/A' }}</span>
685
+ </div>
686
+ <div class="metric-box">
687
+ <label>Max Profit</label>
688
+ <span class="profit">${{ selectedReport.max_profit || 0 }}</span>
689
+ </div>
690
+ <div class="metric-box">
691
+ <label>Max Loss</label>
692
+ <span class="loss">${{ selectedReport.max_loss || 0 }}</span>
693
+ </div>
694
+ </div>
695
+
696
+ <div class="report-section risk-section">
697
+ <h3><ShieldCheck :size="18" /> Risk Analysis</h3>
698
+ <p>{{ selectedReport.risk_warning }}</p>
699
+ </div>
700
+ </div>
701
+ </div>
702
+
703
+ <!-- Footer Disclaimer -->
704
+ <footer class="main-footer glass">
705
+ <div class="footer-content">
706
+ <div class="disclaimer-info">
707
+ <AlertTriangle :size="16" />
708
+ <span><strong>Trading Disclaimer:</strong> Educational and research purposes only. AI-generated insights may be subject to latency or error. Consult a professional advisor before trading.</span>
709
+ </div>
710
+ <div class="copyright">
711
+ &copy; 2024 Agora AI
712
+ </div>
713
+ </div>
714
+ </footer>
715
+ </div>
716
+ </template>
717
+
718
+ <style scoped>
719
+ .app-wrapper {
720
+ display: flex;
721
+ flex-direction: column;
722
+ min-height: 100vh;
723
+ padding: 1.5rem;
724
+ gap: 1.5rem;
725
+ }
726
+
727
+ .main-header {
728
+ display: flex;
729
+ justify-content: space-between;
730
+ align-items: center;
731
+ padding: 1rem 2rem;
732
+ z-index: 100;
733
+ }
734
+
735
+ .logo {
736
+ display: flex;
737
+ align-items: center;
738
+ gap: 1rem;
739
+ }
740
+
741
+ .logo-icon {
742
+ background: var(--accent-gradient);
743
+ padding: 0.5rem;
744
+ border-radius: 0.75rem;
745
+ display: flex;
746
+ align-items: center;
747
+ justify-content: center;
748
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
749
+ }
750
+
751
+ .icon-primary {
752
+ color: white;
753
+ }
754
+
755
+ .btn-danger {
756
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
757
+ border: 1px solid rgba(239, 68, 68, 0.3);
758
+ }
759
+
760
+ .btn-danger:hover:not(:disabled) {
761
+ background: linear-gradient(135deg, #dc2626 0%, #b91c1c 100%);
762
+ box-shadow: 0 8px 24px rgba(239, 68, 68, 0.4);
763
+ transform: translateY(-2px);
764
+ }
765
+
766
+ .logo-text h1 {
767
+ font-size: 1.5rem;
768
+ margin: 0;
769
+ line-height: 1;
770
+ }
771
+
772
+ .logo-text h1 span {
773
+ color: var(--accent-primary);
774
+ font-weight: 400;
775
+ }
776
+
777
+ .logo-text p {
778
+ font-size: 0.75rem;
779
+ color: var(--text-secondary);
780
+ font-weight: 500;
781
+ letter-spacing: 0.1em;
782
+ text-transform: uppercase;
783
+ margin-top: 0.25rem;
784
+ }
785
+
786
+ .controls {
787
+ display: flex;
788
+ gap: 1rem;
789
+ align-items: center;
790
+ }
791
+
792
+ .input-group, .select-group {
793
+ display: flex;
794
+ align-items: center;
795
+ padding: 0.5rem 1rem;
796
+ gap: 0.75rem;
797
+ }
798
+
799
+ .input-group input {
800
+ background: transparent;
801
+ border: none;
802
+ color: var(--text-primary);
803
+ outline: none;
804
+ font-size: 0.9rem;
805
+ width: 200px;
806
+ }
807
+
808
+ .select-group select {
809
+ background: transparent;
810
+ border: none;
811
+ color: var(--text-primary);
812
+ outline: none;
813
+ font-size: 0.9rem;
814
+ cursor: pointer;
815
+ }
816
+
817
+ .select-group select option {
818
+ background-color: var(--bg-color);
819
+ }
820
+
821
+ .dashboard-content {
822
+ display: flex;
823
+ flex-direction: column;
824
+ gap: 1.5rem;
825
+ flex: 1;
826
+ }
827
+
828
+ .grid-layout {
829
+ display: grid;
830
+ grid-template-columns: 400px 1fr;
831
+ gap: 1.5rem;
832
+ flex: 1;
833
+ }
834
+
835
+ .panel-logs {
836
+ grid-column: 2; /* Move to right column */
837
+ display: flex;
838
+ flex-direction: column;
839
+ overflow: hidden;
840
+ height: calc(100vh - 240px);
841
+ }
842
+
843
+ .panel-header {
844
+ padding: 1.25rem;
845
+ border-bottom: 1px solid var(--glass-border);
846
+ display: flex;
847
+ justify-content: space-between;
848
+ align-items: center;
849
+ }
850
+
851
+ .panel-header h3 {
852
+ display: flex;
853
+ align-items: center;
854
+ gap: 0.75rem;
855
+ font-size: 1.1rem;
856
+ color: var(--text-primary);
857
+ }
858
+
859
+ .header-hint {
860
+ font-size: 11px;
861
+ color: var(--text-tertiary);
862
+ background: rgba(255, 255, 255, 0.05);
863
+ padding: 2px 8px;
864
+ border-radius: 4px;
865
+ text-transform: uppercase;
866
+ letter-spacing: 0.5px;
867
+ }
868
+
869
+ .log-viewport {
870
+ flex: 1;
871
+ overflow-y: auto;
872
+ padding: 1.25rem;
873
+ display: flex;
874
+ flex-direction: column;
875
+ gap: 1rem;
876
+ }
877
+
878
+ .log-entry {
879
+ padding: 1.25rem;
880
+ background: rgba(255, 255, 255, 0.02);
881
+ border-radius: 0.75rem;
882
+ border-left: 4px solid var(--accent-primary);
883
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
884
+ border-right: 1px solid rgba(255, 255, 255, 0.05);
885
+ border-bottom: 1px solid rgba(255, 255, 255, 0.03);
886
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
887
+ transition: all 0.2s ease;
888
+ margin-bottom: 0.5rem;
889
+ }
890
+
891
+ .log-entry:hover {
892
+ background: rgba(255, 255, 255, 0.04);
893
+ transform: translateY(-2px);
894
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
895
+ }
896
+
897
+ .log-meta {
898
+ display: flex;
899
+ align-items: center;
900
+ gap: 0.75rem;
901
+ margin-bottom: 0.75rem;
902
+ color: var(--text-secondary);
903
+ font-size: 0.75rem;
904
+ letter-spacing: 0.05em;
905
+ }
906
+
907
+ .log-meta .source {
908
+ font-weight: 800;
909
+ text-transform: uppercase;
910
+ }
911
+
912
+ .log-meta .time {
913
+ margin-left: auto;
914
+ opacity: 0.5;
915
+ font-family: 'JetBrains Mono', monospace;
916
+ font-size: 0.7rem;
917
+ }
918
+
919
+ .log-content {
920
+ font-size: 1rem;
921
+ line-height: 1.6;
922
+ color: var(--text-primary);
923
+ font-weight: 400;
924
+ word-wrap: break-word;
925
+ }
926
+
927
+ .log-content :deep(strong) {
928
+ color: #fff;
929
+ font-weight: 700;
930
+ }
931
+
932
+ .log-content :deep(em) {
933
+ color: var(--text-secondary);
934
+ font-style: italic;
935
+ }
936
+
937
+ .log-content :deep(br) {
938
+ display: block;
939
+ content: "";
940
+ margin-top: 0.5rem;
941
+ }
942
+
943
+ .log-content :deep(ul), .log-content :deep(ol) {
944
+ margin: 0.75rem 0;
945
+ padding-left: 1.5rem;
946
+ }
947
+
948
+ .log-content :deep(li) {
949
+ margin-bottom: 0.4rem;
950
+ }
951
+
952
+ .log-content :deep(h3), .log-content :deep(h4), .log-content :deep(h5) {
953
+ margin: 1.25rem 0 0.75rem 0;
954
+ color: var(--text-primary);
955
+ font-weight: 700;
956
+ line-height: 1.3;
957
+ }
958
+
959
+ .log-content :deep(.inline-code) {
960
+ background: rgba(255, 255, 255, 0.1);
961
+ padding: 0.2rem 0.4rem;
962
+ border-radius: 0.25rem;
963
+ font-family: 'JetBrains Mono', monospace;
964
+ font-size: 0.85em;
965
+ color: var(--accent-primary);
966
+ }
967
+
968
+ .log-content :deep(blockquote) {
969
+ border-left: 3px solid var(--accent-primary);
970
+ background: rgba(255, 255, 255, 0.03);
971
+ padding: 0.75rem 1rem;
972
+ margin: 1rem 0;
973
+ font-style: italic;
974
+ border-radius: 0 0.5rem 0.5rem 0;
975
+ }
976
+
977
+ /* Loading State */
978
+ .loading-state {
979
+ display: flex;
980
+ flex-direction: column;
981
+ align-items: center;
982
+ justify-content: center;
983
+ padding: 3rem 2rem;
984
+ gap: 1.5rem;
985
+ }
986
+
987
+ .sparkles-wrapper {
988
+ position: relative;
989
+ width: 80px;
990
+ height: 80px;
991
+ display: flex;
992
+ align-items: center;
993
+ justify-content: center;
994
+ }
995
+
996
+ .sparkle-icon {
997
+ color: var(--accent-primary);
998
+ animation: sparkleGlow 2s ease-in-out infinite;
999
+ filter: drop-shadow(0 0 12px rgba(59, 130, 246, 0.6));
1000
+ }
1001
+
1002
+ @keyframes sparkleGlow {
1003
+ 0%, 100% {
1004
+ opacity: 1;
1005
+ transform: scale(1) rotate(0deg);
1006
+ }
1007
+ 50% {
1008
+ opacity: 0.7;
1009
+ transform: scale(1.15) rotate(180deg);
1010
+ }
1011
+ }
1012
+
1013
+ .sparkle-particles {
1014
+ position: absolute;
1015
+ width: 100%;
1016
+ height: 100%;
1017
+ top: 0;
1018
+ left: 0;
1019
+ }
1020
+
1021
+ .particle {
1022
+ position: absolute;
1023
+ width: 8px;
1024
+ height: 8px;
1025
+ background: linear-gradient(135deg, #60a5fa, #3b82f6);
1026
+ border-radius: 50%;
1027
+ animation: particleFloat 3s ease-in-out infinite;
1028
+ box-shadow: 0 0 8px rgba(59, 130, 246, 0.8);
1029
+ }
1030
+
1031
+ .particle:nth-child(1) {
1032
+ top: 10%;
1033
+ left: 10%;
1034
+ animation-delay: 0s;
1035
+ }
1036
+
1037
+ .particle:nth-child(2) {
1038
+ top: 10%;
1039
+ right: 10%;
1040
+ animation-delay: 0.75s;
1041
+ }
1042
+
1043
+ .particle:nth-child(3) {
1044
+ bottom: 10%;
1045
+ left: 10%;
1046
+ animation-delay: 1.5s;
1047
+ }
1048
+
1049
+ .particle:nth-child(4) {
1050
+ bottom: 10%;
1051
+ right: 10%;
1052
+ animation-delay: 2.25s;
1053
+ }
1054
+
1055
+ @keyframes particleFloat {
1056
+ 0%, 100% {
1057
+ transform: translateY(0) scale(1);
1058
+ opacity: 0;
1059
+ }
1060
+ 10% {
1061
+ opacity: 1;
1062
+ }
1063
+ 50% {
1064
+ transform: translateY(-20px) scale(1.2);
1065
+ opacity: 1;
1066
+ }
1067
+ 90% {
1068
+ opacity: 1;
1069
+ }
1070
+ 100% {
1071
+ transform: translateY(-40px) scale(0.8);
1072
+ opacity: 0;
1073
+ }
1074
+ }
1075
+
1076
+ .loading-state p {
1077
+ color: var(--text-secondary);
1078
+ font-size: 0.95rem;
1079
+ font-weight: 500;
1080
+ animation: fadeInOut 2s ease-in-out infinite;
1081
+ }
1082
+
1083
+ @keyframes fadeInOut {
1084
+ 0%, 100% { opacity: 0.6; }
1085
+ 50% { opacity: 1; }
1086
+ }
1087
+
1088
+ .panel-results {
1089
+ grid-column: 1; /* Move to left column */
1090
+ grid-row: 1; /* Ensure it stays on the first row */
1091
+ display: flex;
1092
+ flex-direction: column;
1093
+ gap: 1.5rem;
1094
+ }
1095
+
1096
+ .summary-section {
1097
+ flex: 1;
1098
+ display: flex;
1099
+ flex-direction: column;
1100
+ min-height: 400px;
1101
+ }
1102
+
1103
+ .results-list {
1104
+ padding: 1.25rem;
1105
+ display: flex;
1106
+ flex-direction: column;
1107
+ gap: 1rem;
1108
+ }
1109
+
1110
+ .result-card {
1111
+ padding: 1.5rem;
1112
+ border-left: 4px solid var(--accent-primary);
1113
+ }
1114
+
1115
+ .card-header {
1116
+ display: flex;
1117
+ align-items: center;
1118
+ gap: 0.75rem;
1119
+ margin-bottom: 1rem;
1120
+ }
1121
+
1122
+ .ticker-badge {
1123
+ padding: 0.25rem 0.75rem;
1124
+ background: rgba(255, 255, 255, 0.1);
1125
+ border-radius: 0.5rem;
1126
+ font-weight: 800;
1127
+ font-size: 1rem;
1128
+ }
1129
+
1130
+ .decision-badge {
1131
+ padding: 0.25rem 0.75rem;
1132
+ border-radius: 0.5rem;
1133
+ font-weight: 700;
1134
+ font-size: 0.8rem;
1135
+ color: white;
1136
+ }
1137
+
1138
+ .confidence-badge {
1139
+ margin-left: auto;
1140
+ font-size: 0.8rem;
1141
+ color: var(--text-secondary);
1142
+ }
1143
+
1144
+ .card-body h4 {
1145
+ font-size: 1.1rem;
1146
+ margin-bottom: 0.75rem;
1147
+ line-height: 1.4;
1148
+ }
1149
+
1150
+ .entry-info {
1151
+ display: flex;
1152
+ align-items: center;
1153
+ gap: 0.5rem;
1154
+ font-size: 0.85rem;
1155
+ margin-bottom: 1rem;
1156
+ color: var(--text-primary);
1157
+ }
1158
+
1159
+ .risk-info {
1160
+ font-size: 0.85rem;
1161
+ color: var(--text-secondary);
1162
+ display: flex;
1163
+ gap: 0.5rem;
1164
+ font-style: italic;
1165
+ }
1166
+
1167
+ .info-card {
1168
+ padding: 1.25rem;
1169
+ border-left: 4px solid var(--warning);
1170
+ }
1171
+
1172
+ .info-card h4 {
1173
+ color: var(--warning);
1174
+ display: flex;
1175
+ align-items: center;
1176
+ gap: 0.5rem;
1177
+ margin-bottom: 0.5rem;
1178
+ }
1179
+
1180
+ .info-card p {
1181
+ font-size: 0.8rem;
1182
+ color: var(--text-secondary);
1183
+ }
1184
+
1185
+ .error-alert {
1186
+ padding: 1rem 1.5rem;
1187
+ border-left: 4px solid var(--danger);
1188
+ display: flex;
1189
+ align-items: center;
1190
+ gap: 1rem;
1191
+ color: var(--danger);
1192
+ }
1193
+
1194
+ .error-alert p {
1195
+ flex: 1;
1196
+ font-weight: 500;
1197
+ }
1198
+
1199
+ .empty-state {
1200
+ display: flex;
1201
+ flex-direction: column;
1202
+ align-items: center;
1203
+ justify-content: center;
1204
+ height: 100%;
1205
+ color: var(--text-muted);
1206
+ gap: 1rem;
1207
+ }
1208
+
1209
+ .status-indicator {
1210
+ display: flex;
1211
+ align-items: center;
1212
+ gap: 0.5rem;
1213
+ font-size: 0.8rem;
1214
+ font-weight: 600;
1215
+ color: var(--success);
1216
+ }
1217
+
1218
+ .pulse {
1219
+ width: 8px;
1220
+ height: 8px;
1221
+ background: var(--success);
1222
+ border-radius: 50%;
1223
+ box-shadow: 0 0 0 rgba(16, 185, 129, 0.4);
1224
+ animation: pulse 2s infinite;
1225
+ }
1226
+
1227
+ @keyframes pulse {
1228
+ 0% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7); }
1229
+ 70% { transform: scale(1); box-shadow: 0 0 0 10px rgba(16, 185, 129, 0); }
1230
+ 100% { transform: scale(0.95); box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
1231
+ }
1232
+
1233
+ .animate-spin {
1234
+ animation: spin 1s linear infinite;
1235
+ }
1236
+
1237
+ @keyframes spin {
1238
+ from { transform: rotate(0deg); }
1239
+ to { transform: rotate(360deg); }
1240
+ }
1241
+
1242
+ @media (max-width: 1024px) {
1243
+ .grid-layout {
1244
+ grid-template-columns: 1fr;
1245
+ }
1246
+ .panel-logs {
1247
+ height: 500px;
1248
+ }
1249
+ }
1250
+
1251
+ .fundamental-badge {
1252
+ padding: 0.25rem 0.5rem;
1253
+ border-radius: 0.5rem;
1254
+ font-weight: 800;
1255
+ font-size: 0.7rem;
1256
+ text-transform: uppercase;
1257
+ display: flex;
1258
+ align-items: center;
1259
+ gap: 0.3rem;
1260
+ background: rgba(255, 255, 255, 0.05);
1261
+ border: 1px solid var(--glass-border);
1262
+ }
1263
+
1264
+ .fundamental-badge.Strong { color: var(--success); border-color: rgba(16, 185, 129, 0.3); }
1265
+ .fundamental-badge.Stable { color: var(--accent-primary); border-color: rgba(59, 130, 246, 0.3); }
1266
+ .fundamental-badge.Weak { color: var(--danger); border-color: rgba(239, 68, 68, 0.3); }
1267
+
1268
+ .status-badge {
1269
+ font-size: 0.7rem;
1270
+ font-weight: 800;
1271
+ color: var(--accent-primary);
1272
+ letter-spacing: 0.1em;
1273
+ }
1274
+
1275
+ .expiry-badge {
1276
+ padding: 0.25rem 0.5rem;
1277
+ background: rgba(255, 255, 255, 0.1);
1278
+ border-radius: 0.5rem;
1279
+ font-weight: 700;
1280
+ font-size: 0.75rem;
1281
+ color: var(--text-primary);
1282
+ display: flex;
1283
+ align-items: center;
1284
+ gap: 0.4rem;
1285
+ border: 1px solid rgba(255, 255, 255, 0.2);
1286
+ }
1287
+
1288
+ .pulse-border {
1289
+ animation: pulse-border 2s infinite;
1290
+ }
1291
+
1292
+ @keyframes pulse-border {
1293
+ 0% { border-color: var(--glass-border); }
1294
+ 50% { border-color: var(--accent-primary); }
1295
+ 100% { border-color: var(--glass-border); }
1296
+ }
1297
+
1298
+ /* Transitions */
1299
+ .list-enter-active,
1300
+ .list-leave-active {
1301
+ transition: all 0.5s ease;
1302
+ }
1303
+ .list-enter-from,
1304
+ .list-leave-to {
1305
+ opacity: 0;
1306
+ transform: translateX(30px);
1307
+ }
1308
+
1309
+ .transition-container {
1310
+ display: flex;
1311
+ flex-direction: column;
1312
+ gap: 1rem;
1313
+ }
1314
+ .main-footer {
1315
+ margin-top: auto;
1316
+ padding: 1rem 2rem;
1317
+ z-index: 100;
1318
+ }
1319
+
1320
+ .footer-content {
1321
+ display: flex;
1322
+ justify-content: space-between;
1323
+ align-items: center;
1324
+ gap: 2rem;
1325
+ }
1326
+
1327
+ .disclaimer-info {
1328
+ display: flex;
1329
+ align-items: center;
1330
+ gap: 0.75rem;
1331
+ font-size: 0.85rem;
1332
+ color: var(--text-secondary);
1333
+ line-height: 1.4;
1334
+ }
1335
+
1336
+ .disclaimer-info strong {
1337
+ color: var(--warning);
1338
+ }
1339
+
1340
+ .copyright {
1341
+ font-size: 0.8rem;
1342
+ color: var(--text-muted);
1343
+ white-space: nowrap;
1344
+ }
1345
+
1346
+ /* Strategy Legs Table */
1347
+ .legs-container {
1348
+ margin: 1.25rem 0;
1349
+ padding: 1rem;
1350
+ border-radius: 0.75rem;
1351
+ background: rgba(255, 255, 255, 0.02);
1352
+ border: 1px solid rgba(255, 255, 255, 0.05);
1353
+ }
1354
+
1355
+ .legs-header {
1356
+ font-size: 0.7rem;
1357
+ font-weight: 800;
1358
+ text-transform: uppercase;
1359
+ color: var(--text-muted);
1360
+ margin-bottom: 0.75rem;
1361
+ letter-spacing: 0.05em;
1362
+ display: flex;
1363
+ justify-content: space-between;
1364
+ }
1365
+
1366
+ .legs-table {
1367
+ width: 100%;
1368
+ border-collapse: collapse;
1369
+ font-size: 0.85rem;
1370
+ }
1371
+
1372
+ .legs-table th {
1373
+ text-align: left;
1374
+ padding: 0.5rem;
1375
+ color: var(--text-secondary);
1376
+ font-weight: 600;
1377
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
1378
+ }
1379
+
1380
+ .legs-table td {
1381
+ padding: 0.6rem 0.5rem;
1382
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
1383
+ }
1384
+
1385
+ .action-badge {
1386
+ padding: 0.15rem 0.4rem;
1387
+ border-radius: 0.25rem;
1388
+ font-size: 0.7rem;
1389
+ font-weight: 800;
1390
+ }
1391
+
1392
+ .action-badge.buy { background: rgba(16, 185, 129, 0.15); color: var(--success); }
1393
+ .action-badge.sell { background: rgba(239, 68, 68, 0.15); color: var(--danger); }
1394
+
1395
+ .strike-pill {
1396
+ font-weight: 700;
1397
+ color: var(--text-primary);
1398
+ }
1399
+
1400
+ .col-expiry {
1401
+ font-size: 0.75rem;
1402
+ color: var(--text-secondary);
1403
+ }
1404
+
1405
+ .col-price {
1406
+ font-weight: 600;
1407
+ color: var(--accent-primary);
1408
+ }
1409
+
1410
+ .risk-reward-grid {
1411
+ display: grid;
1412
+ grid-template-columns: repeat(3, 1fr);
1413
+ gap: 0.75rem;
1414
+ margin: 1rem 0;
1415
+ }
1416
+
1417
+ .rr-item {
1418
+ padding: 0.75rem;
1419
+ background: rgba(255, 255, 255, 0.03);
1420
+ border-radius: 0.5rem;
1421
+ display: flex;
1422
+ flex-direction: column;
1423
+ gap: 0.25rem;
1424
+ border: 1px solid rgba(255, 255, 255, 0.05);
1425
+ }
1426
+
1427
+ .rr-label {
1428
+ font-size: 0.65rem;
1429
+ font-weight: 700;
1430
+ color: var(--text-muted);
1431
+ text-transform: uppercase;
1432
+ }
1433
+
1434
+ .rr-val {
1435
+ font-size: 1.1rem;
1436
+ font-weight: 800;
1437
+ }
1438
+
1439
+ .rr-val.profit { color: var(--success); }
1440
+ .rr-val.loss { color: var(--danger); }
1441
+
1442
+ .strat-title {
1443
+ margin-bottom: 0.5rem;
1444
+ color: var(--accent-primary);
1445
+ font-weight: 800;
1446
+ }
1447
+
1448
+ .strat-reasoning {
1449
+ font-size: 0.95rem;
1450
+ line-height: 1.5;
1451
+ color: var(--text-secondary);
1452
+ margin-bottom: 1rem;
1453
+ }
1454
+
1455
+ @media (max-width: 1024px) {
1456
+ .footer-content {
1457
+ flex-direction: column;
1458
+ text-align: center;
1459
+ gap: 1rem;
1460
+ }
1461
+ }
1462
+ /* Breadcrumb Styling */
1463
+ .workflow-breadcrumb {
1464
+ display: flex;
1465
+ justify-content: center;
1466
+ align-items: center;
1467
+ padding: 0.75rem 2rem;
1468
+ gap: 1.5rem;
1469
+ margin-bottom: 0.5rem;
1470
+ position: relative;
1471
+ z-index: 50; /* Ensure tooltips appear above content below */
1472
+ }
1473
+
1474
+ .breadcrumb-label {
1475
+ text-align: center;
1476
+ font-size: 0.65rem;
1477
+ font-weight: 800;
1478
+ text-transform: uppercase;
1479
+ color: var(--text-muted);
1480
+ letter-spacing: 0.1em;
1481
+ margin-top: 0.5rem;
1482
+ display: flex;
1483
+ align-items: center;
1484
+ justify-content: center;
1485
+ gap: 0.4rem;
1486
+ opacity: 0.7;
1487
+ }
1488
+
1489
+ .legend-text {
1490
+ font-size: 0.6rem;
1491
+ color: var(--accent-primary);
1492
+ text-transform: none;
1493
+ letter-spacing: normal;
1494
+ margin-left: 0.5rem;
1495
+ font-weight: 500;
1496
+ font-style: italic;
1497
+ opacity: 0.9;
1498
+ }
1499
+
1500
+ .breadcrumb-item {
1501
+ display: flex;
1502
+ align-items: center;
1503
+ gap: 0.75rem;
1504
+ opacity: 0.4;
1505
+ transition: all 0.4s ease;
1506
+ cursor: help;
1507
+ position: relative;
1508
+ }
1509
+
1510
+ /* Fancy Tooltip */
1511
+ .breadcrumb-item::after {
1512
+ content: attr(data-tooltip);
1513
+ position: absolute;
1514
+ top: 140%; /* Move below */
1515
+ left: 50%;
1516
+ transform: translateX(-50%) translateY(-10px);
1517
+ background: rgba(15, 23, 42, 0.95);
1518
+ color: #f1f5f9;
1519
+ padding: 0.6rem 1rem;
1520
+ border-radius: 0.5rem;
1521
+ font-size: 0.75rem;
1522
+ line-height: 1.4;
1523
+ width: max-content;
1524
+ max-width: 220px;
1525
+ text-align: center;
1526
+ border: 1px solid rgba(255, 255, 255, 0.1);
1527
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.5);
1528
+ opacity: 0;
1529
+ visibility: hidden;
1530
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
1531
+ z-index: 1000; /* Ensure high z-index regardless */
1532
+ backdrop-filter: blur(4px);
1533
+ font-weight: 500;
1534
+ pointer-events: none;
1535
+ }
1536
+
1537
+ /* Tooltip Arrow */
1538
+ .breadcrumb-item::before {
1539
+ content: '';
1540
+ position: absolute;
1541
+ top: 140%; /* Move below */
1542
+ left: 50%;
1543
+ transform: translateX(-50%) translateY(-10px);
1544
+ border: 6px solid transparent;
1545
+ border-bottom-color: rgba(15, 23, 42, 0.95); /* Point Up */
1546
+ margin-top: -11px; /* Overlap slightly */
1547
+ opacity: 0;
1548
+ visibility: hidden;
1549
+ transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);
1550
+ z-index: 1000;
1551
+ pointer-events: none;
1552
+ }
1553
+
1554
+ .breadcrumb-item:hover::after,
1555
+ .breadcrumb-item:hover::before {
1556
+ opacity: 1;
1557
+ visibility: visible;
1558
+ transform: translateX(-50%) translateY(0);
1559
+ }
1560
+
1561
+
1562
+ .breadcrumb-item:hover {
1563
+ opacity: 0.8;
1564
+ }
1565
+
1566
+ .breadcrumb-item.active {
1567
+ opacity: 1;
1568
+ transform: scale(1.05);
1569
+ }
1570
+
1571
+ .item-icon-wrapper {
1572
+ position: relative;
1573
+ display: flex;
1574
+ align-items: center;
1575
+ justify-content: center;
1576
+ width: 28px;
1577
+ height: 28px;
1578
+ background: rgba(255, 255, 255, 0.05);
1579
+ border-radius: 50%;
1580
+ border: 1px solid rgba(255, 255, 255, 0.1);
1581
+ }
1582
+
1583
+ .active .item-icon-wrapper {
1584
+ background: rgba(255, 255, 255, 0.15);
1585
+ border-color: currentColor;
1586
+ box-shadow: 0 0 20px currentColor, inset 0 0 10px currentColor; /* Brighter glow */
1587
+ }
1588
+
1589
+ .item-label {
1590
+ font-size: 0.8rem;
1591
+ font-weight: 700;
1592
+ letter-spacing: 0.05em;
1593
+ text-transform: uppercase;
1594
+ color: var(--text-primary);
1595
+ }
1596
+
1597
+ .separator {
1598
+ color: var(--text-muted);
1599
+ margin-left: 0.5rem;
1600
+ }
1601
+
1602
+ .bulb {
1603
+ position: absolute;
1604
+ top: -2px;
1605
+ right: -2px;
1606
+ width: 8px;
1607
+ height: 8px;
1608
+ border-radius: 50%;
1609
+ box-shadow: 0 0 10px currentColor;
1610
+ animation: bulb-pulse 1.5s infinite;
1611
+ }
1612
+
1613
+ @keyframes bulb-pulse {
1614
+ 0% { transform: scale(1); opacity: 1; box-shadow: 0 0 5px currentColor; }
1615
+ 50% { transform: scale(1.4); opacity: 0.8; box-shadow: 0 0 15px currentColor; }
1616
+ 100% { transform: scale(1); opacity: 1; box-shadow: 0 0 5px currentColor; }
1617
+ }
1618
+
1619
+ @media (max-width: 1024px) {
1620
+ .grid-layout {
1621
+ grid-template-columns: 1fr; /* Stack columns on tablet/mobile */
1622
+ }
1623
+
1624
+ .panel-results {
1625
+ grid-column: 1; /* Both take full width */
1626
+ min-height: auto;
1627
+ }
1628
+
1629
+ .panel-logs {
1630
+ grid-column: 1;
1631
+ height: 500px;
1632
+ }
1633
+ }
1634
+
1635
+ .btn-clear {
1636
+ background: transparent;
1637
+ border: 1px solid var(--text-secondary);
1638
+ color: var(--text-secondary);
1639
+ padding: 0.25rem 0.75rem;
1640
+ border-radius: 0.5rem;
1641
+ font-size: 0.75rem;
1642
+ cursor: pointer;
1643
+ transition: all 0.2s;
1644
+ }
1645
+
1646
+ .btn-clear:hover {
1647
+ background: rgba(255,255,255,0.1);
1648
+ color: white;
1649
+ }
1650
+
1651
+ .history-section {
1652
+ width: 100%;
1653
+ margin-top: 2rem;
1654
+ display: flex;
1655
+ flex-direction: column;
1656
+ }
1657
+
1658
+ .history-table-container {
1659
+ margin: 1.25rem;
1660
+ overflow-x: auto;
1661
+ border-radius: 0.5rem;
1662
+ }
1663
+
1664
+ .glass-inset {
1665
+ background: rgba(0, 0, 0, 0.2);
1666
+ border: 1px solid rgba(255,255,255,0.05);
1667
+ box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
1668
+ }
1669
+
1670
+ .history-table {
1671
+ width: 100%;
1672
+ border-collapse: collapse;
1673
+ font-size: 0.9rem;
1674
+ color: var(--text-primary);
1675
+ }
1676
+
1677
+ .history-table th {
1678
+ text-align: left;
1679
+ padding: 1rem;
1680
+ color: var(--text-secondary);
1681
+ font-weight: 600;
1682
+ text-transform: uppercase;
1683
+ font-size: 0.75rem;
1684
+ letter-spacing: 0.05em;
1685
+ border-bottom: 1px solid rgba(255,255,255,0.1);
1686
+ white-space: nowrap;
1687
+ }
1688
+
1689
+ .history-table td {
1690
+ padding: 1rem;
1691
+ border-bottom: 1px solid rgba(255,255,255,0.05);
1692
+ }
1693
+
1694
+ .history-table tbody tr {
1695
+ cursor: pointer;
1696
+ transition: background 0.2s;
1697
+ }
1698
+
1699
+ .history-table tbody tr:hover {
1700
+ background: rgba(255,255,255,0.05);
1701
+ }
1702
+
1703
+ .ticker-pill {
1704
+ font-weight: 700;
1705
+ background: rgba(255,255,255,0.1);
1706
+ padding: 0.2rem 0.5rem;
1707
+ border-radius: 0.25rem;
1708
+ }
1709
+
1710
+ .decision-pill {
1711
+ font-weight: 700;
1712
+ padding: 0.2rem 0.6rem;
1713
+ border-radius: 1rem;
1714
+ border: 1px solid transparent;
1715
+ font-size: 0.8rem;
1716
+ }
1717
+
1718
+ .col-date { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: var(--text-secondary); white-space: nowrap; }
1719
+ .col-profit { font-weight: 700; color: var(--text-secondary); }
1720
+ .col-profit.has-profit { color: var(--success); }
1721
+
1722
+ .model-badge {
1723
+ font-size: 0.7rem;
1724
+ background: rgba(255,255,255,0.05);
1725
+ padding: 0.15rem 0.4rem;
1726
+ border-radius: 0.25rem;
1727
+ color: var(--text-secondary);
1728
+ white-space: nowrap;
1729
+ }
1730
+
1731
+ .confidence-bar-wrapper {
1732
+ display: flex;
1733
+ align-items: center;
1734
+ gap: 0.5rem;
1735
+ }
1736
+
1737
+ .confidence-bar {
1738
+ height: 4px;
1739
+ border-radius: 2px;
1740
+ width: 60px;
1741
+ }
1742
+
1743
+ .col-action { color: var(--text-muted); text-align: right; }
1744
+ .history-table tbody tr:hover .col-action { color: white; }
1745
+
1746
+ /* Modal Styles */
1747
+ .modal-overlay {
1748
+ position: fixed;
1749
+ top: 0;
1750
+ left: 0;
1751
+ right: 0;
1752
+ bottom: 0;
1753
+ background: rgba(0, 0, 0, 0.7);
1754
+ backdrop-filter: blur(4px);
1755
+ z-index: 1000;
1756
+ display: flex;
1757
+ align-items: center;
1758
+ justify-content: center;
1759
+ padding: 1.5rem;
1760
+ }
1761
+
1762
+ .modal-content {
1763
+ background: #0f172a; /* Fallback */
1764
+ background: linear-gradient(135deg, rgba(30, 41, 59, 0.95), rgba(15, 23, 42, 0.98));
1765
+ border: 1px solid rgba(255, 255, 255, 0.1);
1766
+ border-top-width: 4px;
1767
+ border-radius: 1rem;
1768
+ padding: 2rem;
1769
+ width: 100%;
1770
+ max-width: 600px;
1771
+ position: relative;
1772
+ max-height: 90vh;
1773
+ overflow-y: auto;
1774
+ box-shadow: 0 20px 50px rgba(0,0,0,0.5);
1775
+ animation: modalPop 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
1776
+ }
1777
+
1778
+ @keyframes modalPop {
1779
+ 0% { transform: scale(0.9); opacity: 0; }
1780
+ 100% { transform: scale(1); opacity: 1; }
1781
+ }
1782
+
1783
+ .modal-close {
1784
+ position: absolute;
1785
+ top: 1rem;
1786
+ right: 1rem;
1787
+ background: none;
1788
+ border: none;
1789
+ color: var(--text-secondary);
1790
+ cursor: pointer;
1791
+ transition: color 0.2s;
1792
+ }
1793
+
1794
+ .modal-close:hover { color: white; }
1795
+
1796
+ .modal-header { margin-bottom: 1.5rem; }
1797
+ .modal-header h2 { font-size: 1.5rem; margin: 0; }
1798
+ .modal-header .timestamp { font-size: 0.8rem; color: var(--text-secondary); }
1799
+
1800
+ .modal-badges {
1801
+ display: flex;
1802
+ gap: 1rem;
1803
+ margin-bottom: 2rem;
1804
+ flex-wrap: wrap;
1805
+ }
1806
+
1807
+ .decision-badge.large { font-size: 1rem; padding: 0.4rem 1rem; }
1808
+ .confidence-badge.large { font-size: 1rem; padding: 0.4rem 1rem; background: rgba(255,255,255,0.1); border-radius: 0.5rem; font-weight: 600; }
1809
+ .rating-badge { display: flex; align-items: center; gap: 0.5rem; background: rgba(236, 72, 153, 0.2); color: #f472b6; padding: 0.4rem 1rem; border-radius: 0.5rem; font-weight: 700; }
1810
+ .model-badge.large { display: flex; align-items: center; gap: 0.5rem; background: rgba(59, 130, 246, 0.1); color: #60a5fa; padding: 0.4rem 1rem; border-radius: 0.5rem; font-weight: 600; font-size: 0.9rem; border: 1px solid rgba(59, 130, 246, 0.2); }
1811
+
1812
+ .report-section { margin-bottom: 1.5rem; }
1813
+ .report-section h3 { display: flex; align-items: center; gap: 0.5rem; font-size: 1rem; color: var(--text-secondary); margin-bottom: 0.5rem; text-transform: uppercase; letter-spacing: 0.05em; }
1814
+ .highlight-text { font-size: 1.1rem; line-height: 1.5; color: white; }
1815
+
1816
+ .metrics-grid {
1817
+ display: grid;
1818
+ grid-template-columns: repeat(2, 1fr);
1819
+ gap: 1rem;
1820
+ margin-bottom: 1.5rem;
1821
+ }
1822
+
1823
+ .metric-box {
1824
+ background: rgba(255,255,255,0.03);
1825
+ padding: 0.75rem;
1826
+ border-radius: 0.5rem;
1827
+ border: 1px solid rgba(255,255,255,0.05);
1828
+ display: flex;
1829
+ flex-direction: column;
1830
+ gap: 0.25rem;
1831
+ }
1832
+
1833
+ .metric-box label { font-size: 0.75rem; color: var(--text-secondary); text-transform: uppercase; }
1834
+ .metric-box span { font-size: 1rem; font-weight: 600; color: white; }
1835
+ .metric-box .profit { color: var(--success); }
1836
+ .metric-box .loss { color: var(--danger); }
1837
+
1838
+ .risk-section p { color: var(--danger); background: rgba(239, 68, 68, 0.1); padding: 1rem; border-radius: 0.5rem; border: 1px solid rgba(239, 68, 68, 0.2); }
1839
+
1840
+ @media (max-width: 768px) {
1841
+ .container {
1842
+ padding: 0.5rem;
1843
+ }
1844
+
1845
+ header {
1846
+ flex-direction: column;
1847
+ align-items: stretch;
1848
+ gap: 1.5rem;
1849
+ padding: 1.5rem 1rem;
1850
+ height: auto;
1851
+ }
1852
+
1853
+ .logo {
1854
+ justify-content: center;
1855
+ width: 100%;
1856
+ }
1857
+
1858
+ .controls {
1859
+ flex-direction: column;
1860
+ width: 100%;
1861
+ gap: 0.75rem;
1862
+ }
1863
+
1864
+ .input-group,
1865
+ .select-group,
1866
+ .btn-primary,
1867
+ .btn-danger {
1868
+ width: 100%;
1869
+ min-width: 0;
1870
+ }
1871
+
1872
+ .workflow-breadcrumb {
1873
+ flex-wrap: wrap; /* Allow wrapping if needed, or row */
1874
+ overflow-x: visible; /* No scroll needed now */
1875
+ padding-bottom: 0;
1876
+ width: 100%;
1877
+ justify-content: center; /* Center icons */
1878
+ }
1879
+
1880
+ .item-label {
1881
+ display: none; /* Hide text on mobile as requested */
1882
+ }
1883
+
1884
+ .mobile-hide {
1885
+ display: none;
1886
+ }
1887
+ }
1888
+ </style>
src/agora/frontend/src/assets/vue.svg ADDED
src/agora/frontend/src/components/HelloWorld.vue ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup>
2
+ import { ref } from 'vue'
3
+
4
+ defineProps({
5
+ msg: String,
6
+ })
7
+
8
+ const count = ref(0)
9
+ </script>
10
+
11
+ <template>
12
+ <h1>{{ msg }}</h1>
13
+
14
+ <div class="card">
15
+ <button type="button" @click="count++">count is {{ count }}</button>
16
+ <p>
17
+ Edit
18
+ <code>components/HelloWorld.vue</code> to test HMR
19
+ </p>
20
+ </div>
21
+
22
+ <p>
23
+ Check out
24
+ <a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
25
+ >create-vue</a
26
+ >, the official Vue + Vite starter
27
+ </p>
28
+ <p>
29
+ Learn more about IDE Support for Vue in the
30
+ <a
31
+ href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
32
+ target="_blank"
33
+ >Vue Docs Scaling up Guide</a
34
+ >.
35
+ </p>
36
+ <p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
37
+ </template>
38
+
39
+ <style scoped>
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
43
+ </style>
src/agora/frontend/src/main.js ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import { createApp } from 'vue'
2
+ import './style.css'
3
+ import App from './App.vue'
4
+
5
+ createApp(App).mount('#app')
src/agora/frontend/src/style.css ADDED
@@ -0,0 +1,118 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700;800&family=Inter:wght@300;400;500;600;700&display=swap');
2
+
3
+ :root {
4
+ --font-heading: 'Outfit', sans-serif;
5
+ --font-body: 'Inter', sans-serif;
6
+
7
+ --bg-color: #030712;
8
+ --surface-color: #0f172a;
9
+ --glass-bg: rgba(15, 23, 42, 0.6);
10
+ --glass-border: rgba(255, 255, 255, 0.08);
11
+
12
+ --text-primary: #f8fafc;
13
+ --text-secondary: #94a3b8;
14
+ --text-muted: #64748b;
15
+
16
+ --accent-primary: #3b82f6;
17
+ --accent-secondary: #8b5cf6;
18
+ --accent-gradient: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
19
+
20
+ --success: #10b981;
21
+ --warning: #f59e0b;
22
+ --danger: #ef4444;
23
+ }
24
+
25
+ * {
26
+ box-sizing: border-box;
27
+ margin: 0;
28
+ padding: 0;
29
+ }
30
+
31
+ body {
32
+ font-family: var(--font-body);
33
+ background-color: var(--bg-color);
34
+ color: var(--text-primary);
35
+ min-height: 100vh;
36
+ line-height: 1.5;
37
+ background-image:
38
+ radial-gradient(circle at 0% 0%, rgba(59, 130, 246, 0.15) 0%, transparent 40%),
39
+ radial-gradient(circle at 100% 100%, rgba(139, 92, 246, 0.15) 0%, transparent 40%);
40
+ background-attachment: fixed;
41
+ }
42
+
43
+ #app {
44
+ width: 100%;
45
+ }
46
+
47
+ .glass {
48
+ background: var(--glass-bg);
49
+ backdrop-filter: blur(12px);
50
+ -webkit-backdrop-filter: blur(12px);
51
+ border: 1px solid var(--glass-border);
52
+ border-radius: 1.25rem;
53
+ }
54
+
55
+ .container {
56
+ max-width: 1400px;
57
+ margin: 0 auto;
58
+ padding: 2rem;
59
+ }
60
+
61
+ h1, h2, h3, h4 {
62
+ font-family: var(--font-heading);
63
+ }
64
+
65
+ button {
66
+ cursor: pointer;
67
+ font-family: inherit;
68
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
69
+ }
70
+
71
+ .btn {
72
+ display: inline-flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ gap: 0.5rem;
76
+ padding: 0.75rem 1.5rem;
77
+ border-radius: 0.75rem;
78
+ font-weight: 600;
79
+ border: 1px solid transparent;
80
+ }
81
+
82
+ .btn-primary {
83
+ background: var(--accent-gradient);
84
+ color: white;
85
+ box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3);
86
+ }
87
+
88
+ .btn-primary:hover:not(:disabled) {
89
+ transform: translateY(-2px);
90
+ box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4);
91
+ }
92
+
93
+ .btn-primary:active:not(:disabled) {
94
+ transform: translateY(0);
95
+ }
96
+
97
+ .btn-primary:disabled {
98
+ opacity: 0.6;
99
+ cursor: not-allowed;
100
+ }
101
+
102
+ /* Scrollbar Styling */
103
+ ::-webkit-scrollbar {
104
+ width: 8px;
105
+ }
106
+
107
+ ::-webkit-scrollbar-track {
108
+ background: transparent;
109
+ }
110
+
111
+ ::-webkit-scrollbar-thumb {
112
+ background: rgba(255, 255, 255, 0.1);
113
+ border-radius: 10px;
114
+ }
115
+
116
+ ::-webkit-scrollbar-thumb:hover {
117
+ background: rgba(255, 255, 255, 0.2);
118
+ }
src/agora/frontend/vite.config.js ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ // https://vite.dev/config/
5
+ export default defineConfig({
6
+ plugins: [vue()],
7
+ })
uv.lock CHANGED
@@ -2,9 +2,6 @@ version = 1
2
  revision = 3
3
  requires-python = "==3.12.*"
4
 
5
- [options]
6
- prerelease-mode = "allow"
7
-
8
  [[package]]
9
  name = "agenticai"
10
  version = "0.1.0"
@@ -111,7 +108,7 @@ dev = [
111
 
112
  [package.metadata]
113
  requires-dist = [
114
- { name = "aiohttp", specifier = ">=3.8.5" },
115
  { name = "anthropic", specifier = ">=0.49.0" },
116
  { name = "arxiv", specifier = ">=2.3.1" },
117
  { name = "asyncio" },
@@ -151,7 +148,7 @@ requires-dist = [
151
  { name = "langchain-ollama", specifier = ">=1.0.0" },
152
  { name = "langchain-openai", specifier = ">=1.1.14" },
153
  { name = "langchain-text-splitters", specifier = ">=1.1.2" },
154
- { name = "langgraph", specifier = ">=1.0.3" },
155
  { name = "langgraph-checkpoint-sqlite", specifier = ">=3.0.0" },
156
  { name = "langsmith", specifier = ">=0.8.0" },
157
  { name = "llama-index", specifier = ">=0.14.15" },
@@ -188,25 +185,25 @@ requires-dist = [
188
  { name = "python-docx" },
189
  { name = "python-dotenv", specifier = ">=1.2.2" },
190
  { name = "reportlab", specifier = ">=4.4.5" },
191
- { name = "requests", specifier = ">=2.32.3" },
192
  { name = "scikit-learn", specifier = ">=1.7.2" },
193
  { name = "sendgrid" },
194
  { name = "sentence-transformers", specifier = ">=5.1.2" },
195
  { name = "smithery", specifier = ">=0.4.4" },
196
  { name = "speedtest-cli", specifier = ">=2.1.3" },
197
  { name = "sqlalchemy", specifier = ">=2.0.46" },
198
- { name = "streamlit", specifier = ">=1.51.0" },
199
  { name = "textblob", specifier = ">=0.17.1" },
200
  { name = "traceloop-sdk", specifier = ">=0.33.0" },
201
  { name = "wikipedia", specifier = ">=1.4.0" },
202
  { name = "yfinance", specifier = ">=0.2.66" },
203
- { name = "yt-dlp", specifier = ">=2025.11.12" },
204
  ]
205
 
206
  [package.metadata.requires-dev]
207
  dev = [
208
  { name = "ipykernel", specifier = ">=7.1.0" },
209
- { name = "pytest", specifier = ">=8.3.3" },
210
  { name = "pytest-asyncio" },
211
  ]
212
 
@@ -230,7 +227,7 @@ wheels = [
230
 
231
  [[package]]
232
  name = "aiohttp"
233
- version = "3.13.3"
234
  source = { registry = "https://pypi.org/simple" }
235
  dependencies = [
236
  { name = "aiohappyeyeballs" },
@@ -239,27 +236,29 @@ dependencies = [
239
  { name = "frozenlist" },
240
  { name = "multidict" },
241
  { name = "propcache" },
 
242
  { name = "yarl" },
243
  ]
244
- sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" }
245
- wheels = [
246
- { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" },
247
- { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" },
248
- { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" },
249
- { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" },
250
- { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" },
251
- { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" },
252
- { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" },
253
- { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" },
254
- { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" },
255
- { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" },
256
- { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" },
257
- { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" },
258
- { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" },
259
- { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" },
260
- { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" },
261
- { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" },
262
- { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" },
 
263
  ]
264
 
265
  [[package]]
@@ -370,15 +369,15 @@ wheels = [
370
 
371
  [[package]]
372
  name = "arxiv"
373
- version = "2.4.0"
374
  source = { registry = "https://pypi.org/simple" }
375
  dependencies = [
376
- { name = "feedparser" },
377
  { name = "requests" },
378
  ]
379
- sdist = { url = "https://files.pythonhosted.org/packages/8d/aa/dc1c6c633f63fce090e7c067af8c528a5e61218a61c266ff615d46cbde0a/arxiv-2.4.0.tar.gz", hash = "sha256:cabe5470d031aa3f22d2744a7600391c62c3489653f0c62bec9019e62bb0554b", size = 74546, upload-time = "2026-01-05T02:43:16.823Z" }
380
  wheels = [
381
- { url = "https://files.pythonhosted.org/packages/11/63/9e71153b2d48c98f8079c90d7211bc65515cc1ad18c3328c3c0472e68f44/arxiv-2.4.0-py3-none-any.whl", hash = "sha256:c02ccb09a777aaadd75d3bc1d2627894ef9c987c651d0dacd864b9f69fb0569f", size = 12065, upload-time = "2026-01-05T02:43:12.542Z" },
382
  ]
383
 
384
  [[package]]
@@ -829,14 +828,14 @@ wheels = [
829
 
830
  [[package]]
831
  name = "click"
832
- version = "8.3.1"
833
  source = { registry = "https://pypi.org/simple" }
834
  dependencies = [
835
  { name = "colorama", marker = "sys_platform == 'win32'" },
836
  ]
837
- sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
838
  wheels = [
839
- { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
840
  ]
841
 
842
  [[package]]
@@ -893,41 +892,41 @@ wheels = [
893
 
894
  [[package]]
895
  name = "cryptography"
896
- version = "46.0.4"
897
  source = { registry = "https://pypi.org/simple" }
898
  dependencies = [
899
  { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
900
  ]
901
- sdist = { url = "https://files.pythonhosted.org/packages/78/19/f748958276519adf6a0c1e79e7b8860b4830dda55ccdf29f2719b5fc499c/cryptography-46.0.4.tar.gz", hash = "sha256:bfd019f60f8abc2ed1b9be4ddc21cfef059c841d86d710bb69909a688cbb8f59", size = 749301, upload-time = "2026-01-28T00:24:37.379Z" }
902
- wheels = [
903
- { url = "https://files.pythonhosted.org/packages/8d/99/157aae7949a5f30d51fcb1a9851e8ebd5c74bf99b5285d8bb4b8b9ee641e/cryptography-46.0.4-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:281526e865ed4166009e235afadf3a4c4cba6056f99336a99efba65336fd5485", size = 7173686, upload-time = "2026-01-28T00:23:07.515Z" },
904
- { url = "https://files.pythonhosted.org/packages/87/91/874b8910903159043b5c6a123b7e79c4559ddd1896e38967567942635778/cryptography-46.0.4-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f14fba5bf6f4390d7ff8f086c566454bff0411f6d8aa7af79c88b6f9267aecc", size = 4275871, upload-time = "2026-01-28T00:23:09.439Z" },
905
- { url = "https://files.pythonhosted.org/packages/c0/35/690e809be77896111f5b195ede56e4b4ed0435b428c2f2b6d35046fbb5e8/cryptography-46.0.4-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:47bcd19517e6389132f76e2d5303ded6cf3f78903da2158a671be8de024f4cd0", size = 4423124, upload-time = "2026-01-28T00:23:11.529Z" },
906
- { url = "https://files.pythonhosted.org/packages/1a/5b/a26407d4f79d61ca4bebaa9213feafdd8806dc69d3d290ce24996d3cfe43/cryptography-46.0.4-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:01df4f50f314fbe7009f54046e908d1754f19d0c6d3070df1e6268c5a4af09fa", size = 4277090, upload-time = "2026-01-28T00:23:13.123Z" },
907
- { url = "https://files.pythonhosted.org/packages/0c/d8/4bb7aec442a9049827aa34cee1aa83803e528fa55da9a9d45d01d1bb933e/cryptography-46.0.4-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:5aa3e463596b0087b3da0dbe2b2487e9fc261d25da85754e30e3b40637d61f81", size = 4947652, upload-time = "2026-01-28T00:23:14.554Z" },
908
- { url = "https://files.pythonhosted.org/packages/2b/08/f83e2e0814248b844265802d081f2fac2f1cbe6cd258e72ba14ff006823a/cryptography-46.0.4-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0a9ad24359fee86f131836a9ac3bffc9329e956624a2d379b613f8f8abaf5255", size = 4455157, upload-time = "2026-01-28T00:23:16.443Z" },
909
- { url = "https://files.pythonhosted.org/packages/0a/05/19d849cf4096448779d2dcc9bb27d097457dac36f7273ffa875a93b5884c/cryptography-46.0.4-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:dc1272e25ef673efe72f2096e92ae39dea1a1a450dd44918b15351f72c5a168e", size = 3981078, upload-time = "2026-01-28T00:23:17.838Z" },
910
- { url = "https://files.pythonhosted.org/packages/e6/89/f7bac81d66ba7cde867a743ea5b37537b32b5c633c473002b26a226f703f/cryptography-46.0.4-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:de0f5f4ec8711ebc555f54735d4c673fc34b65c44283895f1a08c2b49d2fd99c", size = 4276213, upload-time = "2026-01-28T00:23:19.257Z" },
911
- { url = "https://files.pythonhosted.org/packages/da/9f/7133e41f24edd827020ad21b068736e792bc68eecf66d93c924ad4719fb3/cryptography-46.0.4-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:eeeb2e33d8dbcccc34d64651f00a98cb41b2dc69cef866771a5717e6734dfa32", size = 4912190, upload-time = "2026-01-28T00:23:21.244Z" },
912
- { url = "https://files.pythonhosted.org/packages/a6/f7/6d43cbaddf6f65b24816e4af187d211f0bc536a29961f69faedc48501d8e/cryptography-46.0.4-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3d425eacbc9aceafd2cb429e42f4e5d5633c6f873f5e567077043ef1b9bbf616", size = 4454641, upload-time = "2026-01-28T00:23:22.866Z" },
913
- { url = "https://files.pythonhosted.org/packages/9e/4f/ebd0473ad656a0ac912a16bd07db0f5d85184924e14fc88feecae2492834/cryptography-46.0.4-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91627ebf691d1ea3976a031b61fb7bac1ccd745afa03602275dda443e11c8de0", size = 4405159, upload-time = "2026-01-28T00:23:25.278Z" },
914
- { url = "https://files.pythonhosted.org/packages/d1/f7/7923886f32dc47e27adeff8246e976d77258fd2aa3efdd1754e4e323bf49/cryptography-46.0.4-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2d08bc22efd73e8854b0b7caff402d735b354862f1145d7be3b9c0f740fef6a0", size = 4666059, upload-time = "2026-01-28T00:23:26.766Z" },
915
- { url = "https://files.pythonhosted.org/packages/eb/a7/0fca0fd3591dffc297278a61813d7f661a14243dd60f499a7a5b48acb52a/cryptography-46.0.4-cp311-abi3-win32.whl", hash = "sha256:82a62483daf20b8134f6e92898da70d04d0ef9a75829d732ea1018678185f4f5", size = 3026378, upload-time = "2026-01-28T00:23:28.317Z" },
916
- { url = "https://files.pythonhosted.org/packages/2d/12/652c84b6f9873f0909374864a57b003686c642ea48c84d6c7e2c515e6da5/cryptography-46.0.4-cp311-abi3-win_amd64.whl", hash = "sha256:6225d3ebe26a55dbc8ead5ad1265c0403552a63336499564675b29eb3184c09b", size = 3478614, upload-time = "2026-01-28T00:23:30.275Z" },
917
- { url = "https://files.pythonhosted.org/packages/56/f7/f648fdbb61d0d45902d3f374217451385edc7e7768d1b03ff1d0e5ffc17b/cryptography-46.0.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a9556ba711f7c23f77b151d5798f3ac44a13455cc68db7697a1096e6d0563cab", size = 7169583, upload-time = "2026-01-28T00:23:56.558Z" },
918
- { url = "https://files.pythonhosted.org/packages/d8/cc/8f3224cbb2a928de7298d6ed4790f5ebc48114e02bdc9559196bfb12435d/cryptography-46.0.4-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8bf75b0259e87fa70bddc0b8b4078b76e7fd512fd9afae6c1193bcf440a4dbef", size = 4275419, upload-time = "2026-01-28T00:23:58.364Z" },
919
- { url = "https://files.pythonhosted.org/packages/17/43/4a18faa7a872d00e4264855134ba82d23546c850a70ff209e04ee200e76f/cryptography-46.0.4-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3c268a3490df22270955966ba236d6bc4a8f9b6e4ffddb78aac535f1a5ea471d", size = 4419058, upload-time = "2026-01-28T00:23:59.867Z" },
920
- { url = "https://files.pythonhosted.org/packages/ee/64/6651969409821d791ba12346a124f55e1b76f66a819254ae840a965d4b9c/cryptography-46.0.4-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:812815182f6a0c1d49a37893a303b44eaac827d7f0d582cecfc81b6427f22973", size = 4278151, upload-time = "2026-01-28T00:24:01.731Z" },
921
- { url = "https://files.pythonhosted.org/packages/20/0b/a7fce65ee08c3c02f7a8310cc090a732344066b990ac63a9dfd0a655d321/cryptography-46.0.4-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:a90e43e3ef65e6dcf969dfe3bb40cbf5aef0d523dff95bfa24256be172a845f4", size = 4939441, upload-time = "2026-01-28T00:24:03.175Z" },
922
- { url = "https://files.pythonhosted.org/packages/db/a7/20c5701e2cd3e1dfd7a19d2290c522a5f435dd30957d431dcb531d0f1413/cryptography-46.0.4-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:a05177ff6296644ef2876fce50518dffb5bcdf903c85250974fc8bc85d54c0af", size = 4451617, upload-time = "2026-01-28T00:24:05.403Z" },
923
- { url = "https://files.pythonhosted.org/packages/00/dc/3e16030ea9aa47b63af6524c354933b4fb0e352257c792c4deeb0edae367/cryptography-46.0.4-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:daa392191f626d50f1b136c9b4cf08af69ca8279d110ea24f5c2700054d2e263", size = 3977774, upload-time = "2026-01-28T00:24:06.851Z" },
924
- { url = "https://files.pythonhosted.org/packages/42/c8/ad93f14118252717b465880368721c963975ac4b941b7ef88f3c56bf2897/cryptography-46.0.4-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e07ea39c5b048e085f15923511d8121e4a9dc45cee4e3b970ca4f0d338f23095", size = 4277008, upload-time = "2026-01-28T00:24:08.926Z" },
925
- { url = "https://files.pythonhosted.org/packages/00/cf/89c99698151c00a4631fbfcfcf459d308213ac29e321b0ff44ceeeac82f1/cryptography-46.0.4-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d5a45ddc256f492ce42a4e35879c5e5528c09cd9ad12420828c972951d8e016b", size = 4903339, upload-time = "2026-01-28T00:24:12.009Z" },
926
- { url = "https://files.pythonhosted.org/packages/03/c3/c90a2cb358de4ac9309b26acf49b2a100957e1ff5cc1e98e6c4996576710/cryptography-46.0.4-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:6bb5157bf6a350e5b28aee23beb2d84ae6f5be390b2f8ee7ea179cda077e1019", size = 4451216, upload-time = "2026-01-28T00:24:13.975Z" },
927
- { url = "https://files.pythonhosted.org/packages/96/2c/8d7f4171388a10208671e181ca43cdc0e596d8259ebacbbcfbd16de593da/cryptography-46.0.4-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:dd5aba870a2c40f87a3af043e0dee7d9eb02d4aff88a797b48f2b43eff8c3ab4", size = 4404299, upload-time = "2026-01-28T00:24:16.169Z" },
928
- { url = "https://files.pythonhosted.org/packages/e9/23/cbb2036e450980f65c6e0a173b73a56ff3bccd8998965dea5cc9ddd424a5/cryptography-46.0.4-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:93d8291da8d71024379ab2cb0b5c57915300155ad42e07f76bea6ad838d7e59b", size = 4664837, upload-time = "2026-01-28T00:24:17.629Z" },
929
- { url = "https://files.pythonhosted.org/packages/0a/21/f7433d18fe6d5845329cbdc597e30caf983229c7a245bcf54afecc555938/cryptography-46.0.4-cp38-abi3-win32.whl", hash = "sha256:0563655cb3c6d05fb2afe693340bc050c30f9f34e15763361cf08e94749401fc", size = 3009779, upload-time = "2026-01-28T00:24:20.198Z" },
930
- { url = "https://files.pythonhosted.org/packages/3a/6a/bd2e7caa2facffedf172a45c1a02e551e6d7d4828658c9a245516a598d94/cryptography-46.0.4-cp38-abi3-win_amd64.whl", hash = "sha256:fa0900b9ef9c49728887d1576fd8d9e7e3ea872fa9b25ef9b64888adc434e976", size = 3466633, upload-time = "2026-01-28T00:24:21.851Z" },
931
  ]
932
 
933
  [[package]]
@@ -1192,29 +1191,18 @@ wheels = [
1192
 
1193
  [[package]]
1194
  name = "fastapi"
1195
- version = "0.128.0"
1196
  source = { registry = "https://pypi.org/simple" }
1197
  dependencies = [
1198
  { name = "annotated-doc" },
1199
  { name = "pydantic" },
1200
  { name = "starlette" },
1201
  { name = "typing-extensions" },
 
1202
  ]
1203
- sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" }
1204
- wheels = [
1205
- { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" },
1206
- ]
1207
-
1208
- [[package]]
1209
- name = "feedparser"
1210
- version = "6.0.12"
1211
- source = { registry = "https://pypi.org/simple" }
1212
- dependencies = [
1213
- { name = "sgmllib3k" },
1214
- ]
1215
- sdist = { url = "https://files.pythonhosted.org/packages/dc/79/db7edb5e77d6dfbc54d7d9df72828be4318275b2e580549ff45a962f6461/feedparser-6.0.12.tar.gz", hash = "sha256:64f76ce90ae3e8ef5d1ede0f8d3b50ce26bcce71dd8ae5e82b1cd2d4a5f94228", size = 286579, upload-time = "2025-09-10T13:33:59.486Z" }
1216
  wheels = [
1217
- { url = "https://files.pythonhosted.org/packages/4e/eb/c96d64137e29ae17d83ad2552470bafe3a7a915e85434d9942077d7fd011/feedparser-6.0.12-py3-none-any.whl", hash = "sha256:6bbff10f5a52662c00a2e3f86a38928c37c48f77b3c511aedcd51de933549324", size = 81480, upload-time = "2025-09-10T13:33:58.022Z" },
1218
  ]
1219
 
1220
  [[package]]
@@ -1597,17 +1585,18 @@ wheels = [
1597
 
1598
  [[package]]
1599
  name = "hf-xet"
1600
- version = "1.2.0"
1601
  source = { registry = "https://pypi.org/simple" }
1602
- sdist = { url = "https://files.pythonhosted.org/packages/5e/6e/0f11bacf08a67f7fb5ee09740f2ca54163863b07b70d579356e9222ce5d8/hf_xet-1.2.0.tar.gz", hash = "sha256:a8c27070ca547293b6890c4bf389f713f80e8c478631432962bb7f4bc0bd7d7f", size = 506020, upload-time = "2025-10-24T19:04:32.129Z" }
1603
  wheels = [
1604
- { url = "https://files.pythonhosted.org/packages/96/2d/22338486473df5923a9ab7107d375dbef9173c338ebef5098ef593d2b560/hf_xet-1.2.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:46740d4ac024a7ca9b22bebf77460ff43332868b661186a8e46c227fdae01848", size = 2866099, upload-time = "2025-10-24T19:04:15.366Z" },
1605
- { url = "https://files.pythonhosted.org/packages/7f/8c/c5becfa53234299bc2210ba314eaaae36c2875e0045809b82e40a9544f0c/hf_xet-1.2.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:27df617a076420d8845bea087f59303da8be17ed7ec0cd7ee3b9b9f579dff0e4", size = 2722178, upload-time = "2025-10-24T19:04:13.695Z" },
1606
- { url = "https://files.pythonhosted.org/packages/9a/92/cf3ab0b652b082e66876d08da57fcc6fa2f0e6c70dfbbafbd470bb73eb47/hf_xet-1.2.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3651fd5bfe0281951b988c0facbe726aa5e347b103a675f49a3fa8144c7968fd", size = 3320214, upload-time = "2025-10-24T19:04:03.596Z" },
1607
- { url = "https://files.pythonhosted.org/packages/46/92/3f7ec4a1b6a65bf45b059b6d4a5d38988f63e193056de2f420137e3c3244/hf_xet-1.2.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d06fa97c8562fb3ee7a378dd9b51e343bc5bc8190254202c9771029152f5e08c", size = 3229054, upload-time = "2025-10-24T19:04:01.949Z" },
1608
- { url = "https://files.pythonhosted.org/packages/0b/dd/7ac658d54b9fb7999a0ccb07ad863b413cbaf5cf172f48ebcd9497ec7263/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4c1428c9ae73ec0939410ec73023c4f842927f39db09b063b9482dac5a3bb737", size = 3413812, upload-time = "2025-10-24T19:04:24.585Z" },
1609
- { url = "https://files.pythonhosted.org/packages/92/68/89ac4e5b12a9ff6286a12174c8538a5930e2ed662091dd2572bbe0a18c8a/hf_xet-1.2.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a55558084c16b09b5ed32ab9ed38421e2d87cf3f1f89815764d1177081b99865", size = 3508920, upload-time = "2025-10-24T19:04:26.927Z" },
1610
- { url = "https://files.pythonhosted.org/packages/cb/44/870d44b30e1dcfb6a65932e3e1506c103a8a5aea9103c337e7a53180322c/hf_xet-1.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:e6584a52253f72c9f52f9e549d5895ca7a471608495c4ecaa6cc73dba2b24d69", size = 2905735, upload-time = "2025-10-24T19:04:35.928Z" },
 
1611
  ]
1612
 
1613
  [[package]]
@@ -1719,21 +1708,23 @@ wheels = [
1719
 
1720
  [[package]]
1721
  name = "huggingface-hub"
1722
- version = "0.36.0"
1723
  source = { registry = "https://pypi.org/simple" }
1724
  dependencies = [
 
1725
  { name = "filelock" },
1726
  { name = "fsspec" },
1727
- { name = "hf-xet", marker = "platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
 
1728
  { name = "packaging" },
1729
  { name = "pyyaml" },
1730
- { name = "requests" },
1731
  { name = "tqdm" },
 
1732
  { name = "typing-extensions" },
1733
  ]
1734
- sdist = { url = "https://files.pythonhosted.org/packages/98/63/4910c5fa9128fdadf6a9c5ac138e8b1b6cee4ca44bf7915bbfbce4e355ee/huggingface_hub-0.36.0.tar.gz", hash = "sha256:47b3f0e2539c39bf5cde015d63b72ec49baff67b6931c3d97f3f84532e2b8d25", size = 463358, upload-time = "2025-10-23T12:12:01.413Z" }
1735
  wheels = [
1736
- { url = "https://files.pythonhosted.org/packages/cb/bd/1a875e0d592d447cbc02805fd3fe0f497714d6a2583f59d14fa9ebad96eb/huggingface_hub-0.36.0-py3-none-any.whl", hash = "sha256:7bcc9ad17d5b3f07b57c78e79d527102d08313caa278a641993acddcb894548d", size = 566094, upload-time = "2025-10-23T12:11:59.557Z" },
1737
  ]
1738
 
1739
  [[package]]
@@ -1759,11 +1750,11 @@ wheels = [
1759
 
1760
  [[package]]
1761
  name = "idna"
1762
- version = "3.11"
1763
  source = { registry = "https://pypi.org/simple" }
1764
- 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" }
1765
  wheels = [
1766
- { 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" },
1767
  ]
1768
 
1769
  [[package]]
@@ -2188,16 +2179,16 @@ wheels = [
2188
 
2189
  [[package]]
2190
  name = "langchain-huggingface"
2191
- version = "1.2.0"
2192
  source = { registry = "https://pypi.org/simple" }
2193
  dependencies = [
2194
  { name = "huggingface-hub" },
2195
  { name = "langchain-core" },
2196
  { name = "tokenizers" },
2197
  ]
2198
- sdist = { url = "https://files.pythonhosted.org/packages/67/2c/4fddeb3387baa05b6a95870ad514f649cafb46e0c0ef9caf949d974e55d2/langchain_huggingface-1.2.0.tar.gz", hash = "sha256:18a2d79955271261fb245b233fea6aa29625576e841f2b4f5bee41e51cc70949", size = 255602, upload-time = "2025-12-12T22:19:51.021Z" }
2199
  wheels = [
2200
- { url = "https://files.pythonhosted.org/packages/81/ce/502157ef7390a31cc67e5873ad66e737a25d1d33fcf6936e5c9a0a451409/langchain_huggingface-1.2.0-py3-none-any.whl", hash = "sha256:0ff6a17d3eb36ce2304f446e3285c74b59358703e8f7916c15bfcf9ec7b57bf1", size = 30671, upload-time = "2025-12-12T22:19:50.023Z" },
2201
  ]
2202
 
2203
  [[package]]
@@ -2241,7 +2232,7 @@ wheels = [
2241
 
2242
  [[package]]
2243
  name = "langgraph"
2244
- version = "1.0.7"
2245
  source = { registry = "https://pypi.org/simple" }
2246
  dependencies = [
2247
  { name = "langchain-core" },
@@ -2251,9 +2242,9 @@ dependencies = [
2251
  { name = "pydantic" },
2252
  { name = "xxhash" },
2253
  ]
2254
- sdist = { url = "https://files.pythonhosted.org/packages/72/5b/f72655717c04e33d3b62f21b166dc063d192b53980e9e3be0e2a117f1c9f/langgraph-1.0.7.tar.gz", hash = "sha256:0cfdfee51e6e8cfe503ecc7367c73933437c505b03fa10a85c710975c8182d9a", size = 497098, upload-time = "2026-01-22T16:57:47.303Z" }
2255
  wheels = [
2256
- { url = "https://files.pythonhosted.org/packages/7e/0e/fe80144e3e4048e5d19ccdb91ac547c1a7dc3da8dbd1443e210048194c14/langgraph-1.0.7-py3-none-any.whl", hash = "sha256:9d68e8f8dd8f3de2fec45f9a06de05766d9b075b78fb03171779893b7a52c4d2", size = 157353, upload-time = "2026-01-22T16:57:45.997Z" },
2257
  ]
2258
 
2259
  [[package]]
@@ -2285,15 +2276,15 @@ wheels = [
2285
 
2286
  [[package]]
2287
  name = "langgraph-prebuilt"
2288
- version = "1.0.7"
2289
  source = { registry = "https://pypi.org/simple" }
2290
  dependencies = [
2291
  { name = "langchain-core" },
2292
  { name = "langgraph-checkpoint" },
2293
  ]
2294
- sdist = { url = "https://files.pythonhosted.org/packages/a7/59/711aecd1a50999456850dc328f3cad72b4372d8218838d8d5326f80cb76f/langgraph_prebuilt-1.0.7.tar.gz", hash = "sha256:38e097e06de810de4d0e028ffc0e432bb56d1fb417620fb1dfdc76c5e03e4bf9", size = 163692, upload-time = "2026-01-22T16:45:22.801Z" }
2295
  wheels = [
2296
- { url = "https://files.pythonhosted.org/packages/47/49/5e37abb3f38a17a3487634abc2a5da87c208cc1d14577eb8d7184b25c886/langgraph_prebuilt-1.0.7-py3-none-any.whl", hash = "sha256:e14923516504405bb5edc3977085bc9622c35476b50c1808544490e13871fe7c", size = 35324, upload-time = "2026-01-22T16:45:21.784Z" },
2297
  ]
2298
 
2299
  [[package]]
@@ -4349,11 +4340,11 @@ wheels = [
4349
 
4350
  [[package]]
4351
  name = "pyasn1"
4352
- version = "0.6.2"
4353
  source = { registry = "https://pypi.org/simple" }
4354
- sdist = { url = "https://files.pythonhosted.org/packages/fe/b6/6e630dff89739fcd427e3f72b3d905ce0acb85a45d4ec3e2678718a3487f/pyasn1-0.6.2.tar.gz", hash = "sha256:9b59a2b25ba7e4f8197db7686c09fb33e658b98339fadb826e9512629017833b", size = 146586, upload-time = "2026-01-16T18:04:18.534Z" }
4355
  wheels = [
4356
- { url = "https://files.pythonhosted.org/packages/44/b5/a96872e5184f354da9c84ae119971a0a4c221fe9b27a4d94bd43f2596727/pyasn1-0.6.2-py3-none-any.whl", hash = "sha256:1eb26d860996a18e9b6ed05e7aae0e9fc21619fcee6af91cca9bad4fbea224bf", size = 83371, upload-time = "2026-01-16T18:04:17.174Z" },
4357
  ]
4358
 
4359
  [[package]]
@@ -4519,20 +4510,20 @@ wheels = [
4519
 
4520
  [[package]]
4521
  name = "pygments"
4522
- version = "2.19.2"
4523
  source = { registry = "https://pypi.org/simple" }
4524
- sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
4525
  wheels = [
4526
- { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
4527
  ]
4528
 
4529
  [[package]]
4530
  name = "pyjwt"
4531
- version = "2.11.0"
4532
  source = { registry = "https://pypi.org/simple" }
4533
- sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" }
4534
  wheels = [
4535
- { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" },
4536
  ]
4537
 
4538
  [package.optional-dependencies]
@@ -4634,7 +4625,7 @@ wheels = [
4634
 
4635
  [[package]]
4636
  name = "pytest"
4637
- version = "9.0.2"
4638
  source = { registry = "https://pypi.org/simple" }
4639
  dependencies = [
4640
  { name = "colorama", marker = "sys_platform == 'win32'" },
@@ -4643,9 +4634,9 @@ dependencies = [
4643
  { name = "pluggy" },
4644
  { name = "pygments" },
4645
  ]
4646
- sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" }
4647
  wheels = [
4648
- { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" },
4649
  ]
4650
 
4651
  [[package]]
@@ -4839,7 +4830,7 @@ wheels = [
4839
 
4840
  [[package]]
4841
  name = "requests"
4842
- version = "2.32.5"
4843
  source = { registry = "https://pypi.org/simple" }
4844
  dependencies = [
4845
  { name = "certifi" },
@@ -4847,9 +4838,9 @@ dependencies = [
4847
  { name = "idna" },
4848
  { name = "urllib3" },
4849
  ]
4850
- sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" }
4851
  wheels = [
4852
- { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" },
4853
  ]
4854
 
4855
  [[package]]
@@ -5030,12 +5021,6 @@ wheels = [
5030
  { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" },
5031
  ]
5032
 
5033
- [[package]]
5034
- name = "sgmllib3k"
5035
- version = "1.0.0"
5036
- source = { registry = "https://pypi.org/simple" }
5037
- sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750, upload-time = "2010-08-24T14:33:52.445Z" }
5038
-
5039
  [[package]]
5040
  name = "shellingham"
5041
  version = "1.5.4"
@@ -5182,20 +5167,20 @@ wheels = [
5182
 
5183
  [[package]]
5184
  name = "starlette"
5185
- version = "0.50.0"
5186
  source = { registry = "https://pypi.org/simple" }
5187
  dependencies = [
5188
  { name = "anyio" },
5189
  { name = "typing-extensions" },
5190
  ]
5191
- sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" }
5192
  wheels = [
5193
- { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" },
5194
  ]
5195
 
5196
  [[package]]
5197
  name = "streamlit"
5198
- version = "1.53.1"
5199
  source = { registry = "https://pypi.org/simple" }
5200
  dependencies = [
5201
  { name = "altair" },
@@ -5217,9 +5202,9 @@ dependencies = [
5217
  { name = "typing-extensions" },
5218
  { name = "watchdog", marker = "sys_platform != 'darwin'" },
5219
  ]
5220
- sdist = { url = "https://files.pythonhosted.org/packages/31/cc/347730d06e3950e426bf2ee06eaf9b281e18387bffa82bd69aa0f281eee3/streamlit-1.53.1.tar.gz", hash = "sha256:ae656af3b68b4bb2d669fa977606096f2021bcbaa14a454a290f8e0a37bab277", size = 8650843, upload-time = "2026-01-22T21:39:04.087Z" }
5221
  wheels = [
5222
- { url = "https://files.pythonhosted.org/packages/23/c3/5e6f6a9328d57436b658447b6f05969d435df8d31344e34ec75c3044657b/streamlit-1.53.1-py3-none-any.whl", hash = "sha256:9534d151feea485b69200dd36448f95f418c511e8c81186ceb57133bdf1443f7", size = 9111505, upload-time = "2026-01-22T21:39:01.344Z" },
5223
  ]
5224
 
5225
  [[package]]
@@ -5395,21 +5380,19 @@ wheels = [
5395
 
5396
  [[package]]
5397
  name = "tornado"
5398
- version = "6.5.4"
5399
  source = { registry = "https://pypi.org/simple" }
5400
- sdist = { url = "https://files.pythonhosted.org/packages/37/1d/0a336abf618272d53f62ebe274f712e213f5a03c0b2339575430b8362ef2/tornado-6.5.4.tar.gz", hash = "sha256:a22fa9047405d03260b483980635f0b041989d8bcc9a313f8fe18b411d84b1d7", size = 513632, upload-time = "2025-12-15T19:21:03.836Z" }
5401
  wheels = [
5402
- { url = "https://files.pythonhosted.org/packages/ab/a9/e94a9d5224107d7ce3cc1fab8d5dc97f5ea351ccc6322ee4fb661da94e35/tornado-6.5.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d6241c1a16b1c9e4cc28148b1cda97dd1c6cb4fb7068ac1bedc610768dff0ba9", size = 443909, upload-time = "2025-12-15T19:20:48.382Z" },
5403
- { url = "https://files.pythonhosted.org/packages/db/7e/f7b8d8c4453f305a51f80dbb49014257bb7d28ccb4bbb8dd328ea995ecad/tornado-6.5.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2d50f63dda1d2cac3ae1fa23d254e16b5e38153758470e9956cbc3d813d40843", size = 442163, upload-time = "2025-12-15T19:20:49.791Z" },
5404
- { url = "https://files.pythonhosted.org/packages/ba/b5/206f82d51e1bfa940ba366a8d2f83904b15942c45a78dd978b599870ab44/tornado-6.5.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1cf66105dc6acb5af613c054955b8137e34a03698aa53272dbda4afe252be17", size = 445746, upload-time = "2025-12-15T19:20:51.491Z" },
5405
- { url = "https://files.pythonhosted.org/packages/8e/9d/1a3338e0bd30ada6ad4356c13a0a6c35fbc859063fa7eddb309183364ac1/tornado-6.5.4-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50ff0a58b0dc97939d29da29cd624da010e7f804746621c78d14b80238669335", size = 445083, upload-time = "2025-12-15T19:20:52.778Z" },
5406
- { url = "https://files.pythonhosted.org/packages/50/d4/e51d52047e7eb9a582da59f32125d17c0482d065afd5d3bc435ff2120dc5/tornado-6.5.4-cp39-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fb5e04efa54cf0baabdd10061eb4148e0be137166146fff835745f59ab9f7f", size = 445315, upload-time = "2025-12-15T19:20:53.996Z" },
5407
- { url = "https://files.pythonhosted.org/packages/27/07/2273972f69ca63dbc139694a3fc4684edec3ea3f9efabf77ed32483b875c/tornado-6.5.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9c86b1643b33a4cd415f8d0fe53045f913bf07b4a3ef646b735a6a86047dda84", size = 446003, upload-time = "2025-12-15T19:20:56.101Z" },
5408
- { url = "https://files.pythonhosted.org/packages/d1/83/41c52e47502bf7260044413b6770d1a48dda2f0246f95ee1384a3cd9c44a/tornado-6.5.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:6eb82872335a53dd063a4f10917b3efd28270b56a33db69009606a0312660a6f", size = 445412, upload-time = "2025-12-15T19:20:57.398Z" },
5409
- { url = "https://files.pythonhosted.org/packages/10/c7/bc96917f06cbee182d44735d4ecde9c432e25b84f4c2086143013e7b9e52/tornado-6.5.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6076d5dda368c9328ff41ab5d9dd3608e695e8225d1cd0fd1e006f05da3635a8", size = 445392, upload-time = "2025-12-15T19:20:58.692Z" },
5410
- { url = "https://files.pythonhosted.org/packages/0c/1a/d7592328d037d36f2d2462f4bc1fbb383eec9278bc786c1b111cbbd44cfa/tornado-6.5.4-cp39-abi3-win32.whl", hash = "sha256:1768110f2411d5cd281bac0a090f707223ce77fd110424361092859e089b38d1", size = 446481, upload-time = "2025-12-15T19:21:00.008Z" },
5411
- { url = "https://files.pythonhosted.org/packages/d6/6d/c69be695a0a64fd37a97db12355a035a6d90f79067a3cf936ec2b1dc38cd/tornado-6.5.4-cp39-abi3-win_amd64.whl", hash = "sha256:fa07d31e0cd85c60713f2b995da613588aa03e1303d75705dca6af8babc18ddc", size = 446886, upload-time = "2025-12-15T19:21:01.287Z" },
5412
- { url = "https://files.pythonhosted.org/packages/50/49/8dc3fd90902f70084bd2cd059d576ddb4f8bb44c2c7c0e33a11422acb17e/tornado-6.5.4-cp39-abi3-win_arm64.whl", hash = "sha256:053e6e16701eb6cbe641f308f4c1a9541f91b6261991160391bfc342e8a551a1", size = 445910, upload-time = "2025-12-15T19:21:02.571Z" },
5413
  ]
5414
 
5415
  [[package]]
@@ -5494,7 +5477,7 @@ wheels = [
5494
 
5495
  [[package]]
5496
  name = "transformers"
5497
- version = "4.57.6"
5498
  source = { registry = "https://pypi.org/simple" }
5499
  dependencies = [
5500
  { name = "filelock" },
@@ -5507,10 +5490,11 @@ dependencies = [
5507
  { name = "safetensors" },
5508
  { name = "tokenizers" },
5509
  { name = "tqdm" },
 
5510
  ]
5511
- sdist = { url = "https://files.pythonhosted.org/packages/c4/35/67252acc1b929dc88b6602e8c4a982e64f31e733b804c14bc24b47da35e6/transformers-4.57.6.tar.gz", hash = "sha256:55e44126ece9dc0a291521b7e5492b572e6ef2766338a610b9ab5afbb70689d3", size = 10134912, upload-time = "2026-01-16T10:38:39.284Z" }
5512
  wheels = [
5513
- { url = "https://files.pythonhosted.org/packages/03/b8/e484ef633af3887baeeb4b6ad12743363af7cce68ae51e938e00aaa0529d/transformers-4.57.6-py3-none-any.whl", hash = "sha256:4c9e9de11333ddfe5114bc872c9f370509198acf0b87a832a0ab9458e2bd0550", size = 11993498, upload-time = "2026-01-16T10:38:31.289Z" },
5514
  ]
5515
 
5516
  [[package]]
@@ -5523,17 +5507,29 @@ wheels = [
5523
 
5524
  [[package]]
5525
  name = "typer"
5526
- version = "0.21.1"
5527
  source = { registry = "https://pypi.org/simple" }
5528
  dependencies = [
 
5529
  { name = "click" },
5530
  { name = "rich" },
5531
  { name = "shellingham" },
5532
- { name = "typing-extensions" },
5533
  ]
5534
- sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" }
5535
  wheels = [
5536
- { url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" },
 
 
 
 
 
 
 
 
 
 
 
 
5537
  ]
5538
 
5539
  [[package]]
@@ -5760,14 +5756,14 @@ wheels = [
5760
 
5761
  [[package]]
5762
  name = "werkzeug"
5763
- version = "3.1.5"
5764
  source = { registry = "https://pypi.org/simple" }
5765
  dependencies = [
5766
  { name = "markupsafe" },
5767
  ]
5768
- sdist = { url = "https://files.pythonhosted.org/packages/5a/70/1469ef1d3542ae7c2c7b72bd5e3a4e6ee69d7978fa8a3af05a38eca5becf/werkzeug-3.1.5.tar.gz", hash = "sha256:6a548b0e88955dd07ccb25539d7d0cc97417ee9e179677d22c7041c8f078ce67", size = 864754, upload-time = "2026-01-08T17:49:23.247Z" }
5769
  wheels = [
5770
- { url = "https://files.pythonhosted.org/packages/ad/e4/8d97cca767bcc1be76d16fb76951608305561c6e056811587f36cb1316a8/werkzeug-3.1.5-py3-none-any.whl", hash = "sha256:5111e36e91086ece91f93268bb39b4a35c1e6f1feac762c9c822ded0a4e322dc", size = 225025, upload-time = "2026-01-08T17:49:21.859Z" },
5771
  ]
5772
 
5773
  [[package]]
@@ -5877,11 +5873,11 @@ wheels = [
5877
 
5878
  [[package]]
5879
  name = "yt-dlp"
5880
- version = "2026.1.31"
5881
  source = { registry = "https://pypi.org/simple" }
5882
- sdist = { url = "https://files.pythonhosted.org/packages/93/b3/025d41aa2b5b814c0898a4c5b2a9aa210192f616738aac5a86ef5780f0f6/yt_dlp-2026.1.31.tar.gz", hash = "sha256:16767a3172cbb7183199f4db19e34b77b19e030ab7101b8f26d6c9e6af6f42ae", size = 3099993, upload-time = "2026-02-01T00:12:20.886Z" }
5883
  wheels = [
5884
- { url = "https://files.pythonhosted.org/packages/98/67/d76cddb63710f88fb53f287b713f268579a85bfc81caacbb1b6048b60b85/yt_dlp-2026.1.31-py3-none-any.whl", hash = "sha256:81f8e70c0d111b9572420cfea0ba9b4c2ae783fa1a4237fa767eeb777d4c5a58", size = 3298976, upload-time = "2026-02-01T00:12:18.835Z" },
5885
  ]
5886
 
5887
  [[package]]
 
2
  revision = 3
3
  requires-python = "==3.12.*"
4
 
 
 
 
5
  [[package]]
6
  name = "agenticai"
7
  version = "0.1.0"
 
108
 
109
  [package.metadata]
110
  requires-dist = [
111
+ { name = "aiohttp", specifier = ">=3.14.0" },
112
  { name = "anthropic", specifier = ">=0.49.0" },
113
  { name = "arxiv", specifier = ">=2.3.1" },
114
  { name = "asyncio" },
 
148
  { name = "langchain-ollama", specifier = ">=1.0.0" },
149
  { name = "langchain-openai", specifier = ">=1.1.14" },
150
  { name = "langchain-text-splitters", specifier = ">=1.1.2" },
151
+ { name = "langgraph", specifier = ">=1.0.10rc1" },
152
  { name = "langgraph-checkpoint-sqlite", specifier = ">=3.0.0" },
153
  { name = "langsmith", specifier = ">=0.8.0" },
154
  { name = "llama-index", specifier = ">=0.14.15" },
 
185
  { name = "python-docx" },
186
  { name = "python-dotenv", specifier = ">=1.2.2" },
187
  { name = "reportlab", specifier = ">=4.4.5" },
188
+ { name = "requests", specifier = ">=2.33.0" },
189
  { name = "scikit-learn", specifier = ">=1.7.2" },
190
  { name = "sendgrid" },
191
  { name = "sentence-transformers", specifier = ">=5.1.2" },
192
  { name = "smithery", specifier = ">=0.4.4" },
193
  { name = "speedtest-cli", specifier = ">=2.1.3" },
194
  { name = "sqlalchemy", specifier = ">=2.0.46" },
195
+ { name = "streamlit", specifier = ">=1.54.0" },
196
  { name = "textblob", specifier = ">=0.17.1" },
197
  { name = "traceloop-sdk", specifier = ">=0.33.0" },
198
  { name = "wikipedia", specifier = ">=1.4.0" },
199
  { name = "yfinance", specifier = ">=0.2.66" },
200
+ { name = "yt-dlp", specifier = ">=2026.2.21" },
201
  ]
202
 
203
  [package.metadata.requires-dev]
204
  dev = [
205
  { name = "ipykernel", specifier = ">=7.1.0" },
206
+ { name = "pytest", specifier = ">=9.0.3" },
207
  { name = "pytest-asyncio" },
208
  ]
209
 
 
227
 
228
  [[package]]
229
  name = "aiohttp"
230
+ version = "3.14.0"
231
  source = { registry = "https://pypi.org/simple" }
232
  dependencies = [
233
  { name = "aiohappyeyeballs" },
 
236
  { name = "frozenlist" },
237
  { name = "multidict" },
238
  { name = "propcache" },
239
+ { name = "typing-extensions" },
240
  { name = "yarl" },
241
  ]
242
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/ab/93ce242f899b68c51b0578c027aafa791ab3614cb9345fa5d37b5f5c8e3e/aiohttp-3.14.0.tar.gz", hash = "sha256:2882de819734c715fd1b9c11c97e09fa020d14438203d1d354d8ed1702791c9b", size = 7940674, upload-time = "2026-06-01T19:41:02.763Z" }
243
+ wheels = [
244
+ { url = "https://files.pythonhosted.org/packages/89/97/2b6889bfb6b6847520d50d95eb8c4307a45e28aaca39faf4a9454b3d1b2f/aiohttp-3.14.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b29518c9c2ec7e373e68259206a137c7f4f5439c58baaec4b5ab3ab799850a4e", size = 750194, upload-time = "2026-06-01T19:37:48.164Z" },
245
+ { url = "https://files.pythonhosted.org/packages/21/e2/62634b7fff918ed98c3c6b2f0e70d520f7f28846cb412d451b04354c6459/aiohttp-3.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:dbec68ce61b64cb73cab4d33df9433427b1713c8bcccb181dce695c1b6f8e87c", size = 506966, upload-time = "2026-06-01T19:37:50.014Z" },
246
+ { url = "https://files.pythonhosted.org/packages/dd/fb/5ce075150828c797a5106f1c2fb26034e709d4289b9d2bf8b07f1e59fac6/aiohttp-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3cdf534aa455593e589302990c5097aa5c92c06c4262a20da22934f9186a5fff", size = 507527, upload-time = "2026-06-01T19:37:51.96Z" },
247
+ { url = "https://files.pythonhosted.org/packages/01/d5/405a0ae4e6b081754a3609c1c97c63a950e000a2def16046f1e736933a0e/aiohttp-3.14.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb6c657104393b5fbff01a5f59b2023db74058a8077d94475d6c25d03882a108", size = 1762420, upload-time = "2026-06-01T19:37:53.839Z" },
248
+ { url = "https://files.pythonhosted.org/packages/ae/1d/e05a7c896b15a6bc6fb8fc5319eb437861c2c49c34559ef928add6590315/aiohttp-3.14.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:46fbbec4e4fab7428d4396a3823f9320e4560aa3113b89eeebce712c27c9ed5a", size = 1733672, upload-time = "2026-06-01T19:37:55.791Z" },
249
+ { url = "https://files.pythonhosted.org/packages/cc/22/a72f7c459e195fa41bf4f7abd1f925b91fe91f8097e51c654229ba144a33/aiohttp-3.14.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2c2c7e05dd5335b298085abf45ddf98673934c3ee1c083d0b9ea13d4186ad500", size = 1805064, upload-time = "2026-06-01T19:37:57.931Z" },
250
+ { url = "https://files.pythonhosted.org/packages/80/50/e85bdaba0be59ca4838005ebfef4048fcdd5f35a02b07057a9a123394440/aiohttp-3.14.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3c7139100fbaae76515b73051d8f0aa3a3ff02e415eec8a8eee8e2223d9ba955", size = 1902125, upload-time = "2026-06-01T19:38:00.225Z" },
251
+ { url = "https://files.pythonhosted.org/packages/19/d8/51de5c6b971c27bb1ef620293b8d1ca611ec78736b34b3f6ccf68e4c8785/aiohttp-3.14.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78d6f9286a629ce52728430afe18f8ed2b6c39a1fddb3802d7244b9983910ad2", size = 1783112, upload-time = "2026-06-01T19:38:02.641Z" },
252
+ { url = "https://files.pythonhosted.org/packages/73/ae/b4402bfde77e43dfb1b6ccff83c7b7ab63ed06b50c4754f0c5423fb374fe/aiohttp-3.14.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cc3c3e12cdaeb92d7dcf13db00e9f6b1956b910e47256e696df1cfa946d02159", size = 1586356, upload-time = "2026-06-01T19:38:04.637Z" },
253
+ { url = "https://files.pythonhosted.org/packages/bc/05/750a3265ca4dc54a460bd0cb1121a8f2ce9171fce4a135fb47ea7fd594d2/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d6a998191f5ebe3b8c28463ff72bc030250008b3193c402464efadd08b5ca02", size = 1723119, upload-time = "2026-06-01T19:38:06.713Z" },
254
+ { url = "https://files.pythonhosted.org/packages/37/01/8c0812c50b3b1b1c37b323bf170d6be8847a8f234060485b7d1e71953f60/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0fc2b75ae8d169d853be2862d960be8550da6c5c65711d5476407eb3fdb006bd", size = 1757216, upload-time = "2026-06-01T19:38:08.736Z" },
255
+ { url = "https://files.pythonhosted.org/packages/47/2a/50fb98028a26887cbe48dcc1df92a90825615bc73b5584301304090cded8/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:16eee56bcc72d04600bc56c1759982c2385ec0b41d3fd3521f836bf64a0957ef", size = 1770500, upload-time = "2026-06-01T19:38:11.111Z" },
256
+ { url = "https://files.pythonhosted.org/packages/bd/32/0ffd598a2fa2b9a423daf242e700cfdabda35d6e602394ad9ae58972c1c7/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5a2e7ca615c3ddc15b82687e05a624e5f5cba3f1d6c20cb81172d70ea498451e", size = 1576224, upload-time = "2026-06-01T19:38:13.391Z" },
257
+ { url = "https://files.pythonhosted.org/packages/0b/f9/b9fc381dd9b66afb33f2634c40e229d106467be0afcabe79648631ab6712/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0b7b8bbbec3ce9467ee0ebe334622fd90624f593edd3136c567811453fc4fae", size = 1794252, upload-time = "2026-06-01T19:38:15.498Z" },
258
+ { url = "https://files.pythonhosted.org/packages/a8/fb/05d9214c975f23225a8cd5c439325e338c7c377b315480ef3871db51f54e/aiohttp-3.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ba10966d4f03dd96a14365be4b8e37c327c76f11c3ca867116966cdd9f98066", size = 1760193, upload-time = "2026-06-01T19:38:17.624Z" },
259
+ { url = "https://files.pythonhosted.org/packages/d9/4b/02992fc4fb9e1b6673ee3f888a8e587a6447afda1f6f4aca776c148c2876/aiohttp-3.14.0-cp312-cp312-win32.whl", hash = "sha256:101df7779c80c0636014a6b2c6642acd3efb5b355d48347c9d7dfb720aee9430", size = 448650, upload-time = "2026-06-01T19:38:19.545Z" },
260
+ { url = "https://files.pythonhosted.org/packages/39/e9/246532214c3abda518477cbaaf16d420295ad8effa5233844cbb38f299ab/aiohttp-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:b0a5747586d4467efd1f932710b269131c9717a872dce082cd92a00c1c13123a", size = 476145, upload-time = "2026-06-01T19:38:21.505Z" },
261
+ { url = "https://files.pythonhosted.org/packages/2b/c3/63f8c20090048915711598b0adf475b149216d736157961de06480a45b15/aiohttp-3.14.0-cp312-cp312-win_arm64.whl", hash = "sha256:5f1c5be60add78fabb4aacd13c5a348ae79d2fcbfc7fa78da8f1eb192273b370", size = 444250, upload-time = "2026-06-01T19:38:24.027Z" },
262
  ]
263
 
264
  [[package]]
 
369
 
370
  [[package]]
371
  name = "arxiv"
372
+ version = "4.0.0"
373
  source = { registry = "https://pypi.org/simple" }
374
  dependencies = [
375
+ { name = "lxml" },
376
  { name = "requests" },
377
  ]
378
+ sdist = { url = "https://files.pythonhosted.org/packages/fb/48/88c8e9c42712760ca9e74e52f6c4a388ee9e9939e341bfd8da295a9d1b17/arxiv-4.0.0.tar.gz", hash = "sha256:1d30a1dba5054e0df9b1d63f8e190b58e6a59d0c2f4ccec344ce1de5bafe546d", size = 198009, upload-time = "2026-05-17T23:42:30.952Z" }
379
  wheels = [
380
+ { url = "https://files.pythonhosted.org/packages/af/50/4d01d219958b19b5aaca6ae74820b181baea438cd034d5b3c04b4cf4f75e/arxiv-4.0.0-py3-none-any.whl", hash = "sha256:fc7e65e74d0fba21df2c521df24119d015fcb839dd5c4feb683ee35548c932c4", size = 13014, upload-time = "2026-05-17T23:42:29.246Z" },
381
  ]
382
 
383
  [[package]]
 
828
 
829
  [[package]]
830
  name = "click"
831
+ version = "8.4.1"
832
  source = { registry = "https://pypi.org/simple" }
833
  dependencies = [
834
  { name = "colorama", marker = "sys_platform == 'win32'" },
835
  ]
836
+ sdist = { url = "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz", hash = "sha256:918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96", size = 353007, upload-time = "2026-05-22T04:08:37.769Z" }
837
  wheels = [
838
+ { url = "https://files.pythonhosted.org/packages/c7/0d/67e5b4109ea4a837e80daa87c2c696711955e40449a97e8926672534def2/click-8.4.1-py3-none-any.whl", hash = "sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2", size = 116639, upload-time = "2026-05-22T04:08:35.26Z" },
839
  ]
840
 
841
  [[package]]
 
892
 
893
  [[package]]
894
  name = "cryptography"
895
+ version = "46.0.7"
896
  source = { registry = "https://pypi.org/simple" }
897
  dependencies = [
898
  { name = "cffi", marker = "platform_python_implementation != 'PyPy'" },
899
  ]
900
+ sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" }
901
+ wheels = [
902
+ { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" },
903
+ { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" },
904
+ { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" },
905
+ { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" },
906
+ { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" },
907
+ { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" },
908
+ { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" },
909
+ { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" },
910
+ { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" },
911
+ { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" },
912
+ { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" },
913
+ { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" },
914
+ { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" },
915
+ { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" },
916
+ { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" },
917
+ { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" },
918
+ { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" },
919
+ { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" },
920
+ { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" },
921
+ { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" },
922
+ { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" },
923
+ { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" },
924
+ { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" },
925
+ { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" },
926
+ { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" },
927
+ { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" },
928
+ { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" },
929
+ { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" },
930
  ]
931
 
932
  [[package]]
 
1191
 
1192
  [[package]]
1193
  name = "fastapi"
1194
+ version = "0.136.3"
1195
  source = { registry = "https://pypi.org/simple" }
1196
  dependencies = [
1197
  { name = "annotated-doc" },
1198
  { name = "pydantic" },
1199
  { name = "starlette" },
1200
  { name = "typing-extensions" },
1201
+ { name = "typing-inspection" },
1202
  ]
1203
+ sdist = { url = "https://files.pythonhosted.org/packages/81/2d/ff8d91d7b564d464629a0fd50a4489c97fcb836ac230bf3a7269232a9b1f/fastapi-0.136.3.tar.gz", hash = "sha256:e487fae93ad408e6f47641ee4dfe389864fd7bec92e547ea8498fc13f43e83ab", size = 396410, upload-time = "2026-05-23T18:53:15.192Z" }
 
 
 
 
 
 
 
 
 
 
 
 
1204
  wheels = [
1205
+ { url = "https://files.pythonhosted.org/packages/e0/82/45359b62a067409bd929ae8a56b8ed13e5a8c8a61194b3c236920999ab83/fastapi-0.136.3-py3-none-any.whl", hash = "sha256:3d2a69bdf04b7e9f3afa292c3bc7a98816bbfafa10bc9b45f3f3700d2f761620", size = 117481, upload-time = "2026-05-23T18:53:16.924Z" },
1206
  ]
1207
 
1208
  [[package]]
 
1585
 
1586
  [[package]]
1587
  name = "hf-xet"
1588
+ version = "1.5.0"
1589
  source = { registry = "https://pypi.org/simple" }
1590
+ sdist = { url = "https://files.pythonhosted.org/packages/74/d8/5c06fc76461418326a7decf8367480c35be11a41fd938633929c60a9ec6b/hf_xet-1.5.0.tar.gz", hash = "sha256:e0fb0a34d9f406eed88233e829a67ec016bec5af19e480eac65a233ea289a948", size = 837196, upload-time = "2026-05-06T06:18:15.583Z" }
1591
  wheels = [
1592
+ { url = "https://files.pythonhosted.org/packages/3d/fb/69ff198a82cae7eb1a69fb84d93b3a3e4816564d76817fe541ddc96874eb/hf_xet-1.5.0-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dad0dc84e941b8ba3c860659fe1fdc35c049d47cce293f003287757e971a8f56", size = 4030814, upload-time = "2026-05-06T06:17:57.933Z" },
1593
+ { url = "https://files.pythonhosted.org/packages/9b/ff/edcc2b40162bef3ff78e14ab637e5f3b89243d6aee72f5949d3bb6a5af83/hf_xet-1.5.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fd6e5a9b0fdac4ed03ed45ef79254a655b1aaab514a02202617fbf643f5fdf7a", size = 3798444, upload-time = "2026-05-06T06:17:55.79Z" },
1594
+ { url = "https://files.pythonhosted.org/packages/49/4d/103f76b04310e5e57656696cc184690d20c466af0bca3ca88f8c8ea5d4f3/hf_xet-1.5.0-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3531b1823a0e6d77d80f9ed15ca0e00f0d115094f8ac033d5cae88f4564cc949", size = 4465986, upload-time = "2026-05-06T06:17:44.886Z" },
1595
+ { url = "https://files.pythonhosted.org/packages/c4/a2/546f47f464737b3edbab6f8ddb57f2599b93d2cbb66f06abb475ccb48651/hf_xet-1.5.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9a0ee58cd18d5ea799f7ed11290bbccbe56bdd8b1d97ca74b9cc49a3945d7a3b", size = 4259865, upload-time = "2026-05-06T06:17:42.639Z" },
1596
+ { url = "https://files.pythonhosted.org/packages/95/7f/1be593c1f28613be2e196473481cd81bfc5910795e30a34e8f744f6cac4f/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1e60df5a42e9bed8628b6416af2cba4cba57ae9f02de226a06b020d98e1aab18", size = 4459835, upload-time = "2026-05-06T06:18:08.026Z" },
1597
+ { url = "https://files.pythonhosted.org/packages/aa/b2/703569fc881f3284487e68cda7b42179978480da3c438042a6bbbb4a671c/hf_xet-1.5.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4b35549ce62601b84da4ff9b24d970032ace3d4430f52d91bcbb26c901d6c690", size = 4672414, upload-time = "2026-05-06T06:18:09.864Z" },
1598
+ { url = "https://files.pythonhosted.org/packages/af/37/1b6def445c567286b50aa3b33828158e135b1be44938dde59f11382a500c/hf_xet-1.5.0-cp37-abi3-win_amd64.whl", hash = "sha256:2806c7c17b4d23f8d88f7c4814f838c3b6150773fe339c20af23e1cfaf2797e4", size = 3977238, upload-time = "2026-05-06T06:18:23.621Z" },
1599
+ { url = "https://files.pythonhosted.org/packages/62/94/3b66b148778ee100dcfd69c2ca22b57b41b44d3063ceec934f209e9184ce/hf_xet-1.5.0-cp37-abi3-win_arm64.whl", hash = "sha256:b6c9df403040248c76d808d3e047d64db2d923bae593eb244c41e425cf6cd7be", size = 3806916, upload-time = "2026-05-06T06:18:21.7Z" },
1600
  ]
1601
 
1602
  [[package]]
 
1708
 
1709
  [[package]]
1710
  name = "huggingface-hub"
1711
+ version = "1.18.0"
1712
  source = { registry = "https://pypi.org/simple" }
1713
  dependencies = [
1714
+ { name = "click" },
1715
  { name = "filelock" },
1716
  { name = "fsspec" },
1717
+ { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" },
1718
+ { name = "httpx" },
1719
  { name = "packaging" },
1720
  { name = "pyyaml" },
 
1721
  { name = "tqdm" },
1722
+ { name = "typer" },
1723
  { name = "typing-extensions" },
1724
  ]
1725
+ sdist = { url = "https://files.pythonhosted.org/packages/fb/d8/748ea0a47f0fa15227fe682f7a80826b4b7c096e4818044b8f56d6cb66d6/huggingface_hub-1.18.0.tar.gz", hash = "sha256:f0c5ecd1ef8c6a60f86f61ee278f2c1570ba9e279c9f54de9094210723b3613b", size = 812699, upload-time = "2026-06-05T09:26:33.401Z" }
1726
  wheels = [
1727
+ { url = "https://files.pythonhosted.org/packages/0b/03/40a05316cb6616e5b7efd7773656441ab04b4b022c2199e79bb4622a92a3/huggingface_hub-1.18.0-py3-none-any.whl", hash = "sha256:729be4a976fb706dcc02d176bcda8a3f32bdf21a294e8f4b3dda6fbcbc9c1ab1", size = 684411, upload-time = "2026-06-05T09:26:31.48Z" },
1728
  ]
1729
 
1730
  [[package]]
 
1750
 
1751
  [[package]]
1752
  name = "idna"
1753
+ version = "3.15"
1754
  source = { registry = "https://pypi.org/simple" }
1755
+ sdist = { url = "https://files.pythonhosted.org/packages/82/77/7b3966d0b9d1d31a36ddf1746926a11dface89a83409bf1483f0237aa758/idna-3.15.tar.gz", hash = "sha256:ca962446ea538f7092a95e057da437618e886f4d349216d2b1e294abfdb65fdc", size = 199245, upload-time = "2026-05-12T22:45:57.011Z" }
1756
  wheels = [
1757
+ { url = "https://files.pythonhosted.org/packages/d2/23/408243171aa9aaba178d3e2559159c24c1171a641aa83b67bdd3394ead8e/idna-3.15-py3-none-any.whl", hash = "sha256:048adeaf8c2d788c40fee287673ccaa74c24ffd8dcf09ffa555a2fbb59f10ac8", size = 72340, upload-time = "2026-05-12T22:45:55.733Z" },
1758
  ]
1759
 
1760
  [[package]]
 
2179
 
2180
  [[package]]
2181
  name = "langchain-huggingface"
2182
+ version = "1.2.2"
2183
  source = { registry = "https://pypi.org/simple" }
2184
  dependencies = [
2185
  { name = "huggingface-hub" },
2186
  { name = "langchain-core" },
2187
  { name = "tokenizers" },
2188
  ]
2189
+ sdist = { url = "https://files.pythonhosted.org/packages/2c/e8/4068ad02179253f55958e59e442e5b6e8cb95ffc5e805cc4db0b1ef61d4e/langchain_huggingface-1.2.2.tar.gz", hash = "sha256:1dd91ec415190d2704e93ec149618e3145075863ba37e74afc9080d685dc2743", size = 255513, upload-time = "2026-04-16T19:57:41.046Z" }
2190
  wheels = [
2191
+ { url = "https://files.pythonhosted.org/packages/0a/ed/648b87f9b67153ade616f360bf4145b76ed428b4adb89525938f611e9828/langchain_huggingface-1.2.2-py3-none-any.whl", hash = "sha256:f94944b0c0d5afc687568d426c87ed5236907464c41e72108ed76eee1a690f6d", size = 31926, upload-time = "2026-04-16T19:57:40.079Z" },
2192
  ]
2193
 
2194
  [[package]]
 
2232
 
2233
  [[package]]
2234
  name = "langgraph"
2235
+ version = "1.0.10rc1"
2236
  source = { registry = "https://pypi.org/simple" }
2237
  dependencies = [
2238
  { name = "langchain-core" },
 
2242
  { name = "pydantic" },
2243
  { name = "xxhash" },
2244
  ]
2245
+ sdist = { url = "https://files.pythonhosted.org/packages/59/33/34c8ab47938ac2ac6df1d2696e28b6000e98c2a783b89655fe2261b7f93b/langgraph-1.0.10rc1.tar.gz", hash = "sha256:4042dc1f33297ccbd593bddc5a4e77dc7e0f37c7ac19d48551c53a22287bacaa", size = 511667, upload-time = "2026-02-26T20:13:38.38Z" }
2246
  wheels = [
2247
+ { url = "https://files.pythonhosted.org/packages/df/61/b6e7fd50c70116369874d681e3aa14bf32adbdde6c05014e5260c081452f/langgraph-1.0.10rc1-py3-none-any.whl", hash = "sha256:10750c035cc48b6809a4657ad0c9fd63fb6ee47dd2f1f2a57adb940381d096e5", size = 160950, upload-time = "2026-02-26T20:13:37.223Z" },
2248
  ]
2249
 
2250
  [[package]]
 
2276
 
2277
  [[package]]
2278
  name = "langgraph-prebuilt"
2279
+ version = "1.0.10"
2280
  source = { registry = "https://pypi.org/simple" }
2281
  dependencies = [
2282
  { name = "langchain-core" },
2283
  { name = "langgraph-checkpoint" },
2284
  ]
2285
+ sdist = { url = "https://files.pythonhosted.org/packages/fe/c8/01471b1b5601f2e9c9a69c39fc9a2fb8611613ede0002e5a2b81c0acd850/langgraph_prebuilt-1.0.10.tar.gz", hash = "sha256:5a6fc513f8907074563b6218ff991c4ed9db19ac63101314919686e8029ddb07", size = 169769, upload-time = "2026-04-17T17:59:45.373Z" }
2286
  wheels = [
2287
+ { url = "https://files.pythonhosted.org/packages/50/49/d073375beabdc6955df6cbe570ba7786836bd4c817ae998955d35037f2fd/langgraph_prebuilt-1.0.10-py3-none-any.whl", hash = "sha256:e3baa1977d819982e690a357ba5bb77ccc1d4d8d4a029c48e502a3b6d171185f", size = 36086, upload-time = "2026-04-17T17:59:44.395Z" },
2288
  ]
2289
 
2290
  [[package]]
 
4340
 
4341
  [[package]]
4342
  name = "pyasn1"
4343
+ version = "0.6.3"
4344
  source = { registry = "https://pypi.org/simple" }
4345
+ sdist = { url = "https://files.pythonhosted.org/packages/5c/5f/6583902b6f79b399c9c40674ac384fd9cd77805f9e6205075f828ef11fb2/pyasn1-0.6.3.tar.gz", hash = "sha256:697a8ecd6d98891189184ca1fa05d1bb00e2f84b5977c481452050549c8a72cf", size = 148685, upload-time = "2026-03-17T01:06:53.382Z" }
4346
  wheels = [
4347
+ { url = "https://files.pythonhosted.org/packages/5d/a0/7d793dce3fa811fe047d6ae2431c672364b462850c6235ae306c0efd025f/pyasn1-0.6.3-py3-none-any.whl", hash = "sha256:a80184d120f0864a52a073acc6fc642847d0be408e7c7252f31390c0f4eadcde", size = 83997, upload-time = "2026-03-17T01:06:52.036Z" },
4348
  ]
4349
 
4350
  [[package]]
 
4510
 
4511
  [[package]]
4512
  name = "pygments"
4513
+ version = "2.20.0"
4514
  source = { registry = "https://pypi.org/simple" }
4515
+ sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
4516
  wheels = [
4517
+ { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
4518
  ]
4519
 
4520
  [[package]]
4521
  name = "pyjwt"
4522
+ version = "2.12.0"
4523
  source = { registry = "https://pypi.org/simple" }
4524
+ sdist = { url = "https://files.pythonhosted.org/packages/a8/10/e8192be5f38f3e8e7e046716de4cae33d56fd5ae08927a823bb916be36c1/pyjwt-2.12.0.tar.gz", hash = "sha256:2f62390b667cd8257de560b850bb5a883102a388829274147f1d724453f8fb02", size = 102511, upload-time = "2026-03-12T17:15:30.831Z" }
4525
  wheels = [
4526
+ { url = "https://files.pythonhosted.org/packages/15/70/70f895f404d363d291dcf62c12c85fdd47619ad9674ac0f53364d035925a/pyjwt-2.12.0-py3-none-any.whl", hash = "sha256:9bb459d1bdd0387967d287f5656bf7ec2b9a26645d1961628cda1764e087fd6e", size = 29700, upload-time = "2026-03-12T17:15:29.257Z" },
4527
  ]
4528
 
4529
  [package.optional-dependencies]
 
4625
 
4626
  [[package]]
4627
  name = "pytest"
4628
+ version = "9.0.3"
4629
  source = { registry = "https://pypi.org/simple" }
4630
  dependencies = [
4631
  { name = "colorama", marker = "sys_platform == 'win32'" },
 
4634
  { name = "pluggy" },
4635
  { name = "pygments" },
4636
  ]
4637
+ sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" }
4638
  wheels = [
4639
+ { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" },
4640
  ]
4641
 
4642
  [[package]]
 
4830
 
4831
  [[package]]
4832
  name = "requests"
4833
+ version = "2.33.0"
4834
  source = { registry = "https://pypi.org/simple" }
4835
  dependencies = [
4836
  { name = "certifi" },
 
4838
  { name = "idna" },
4839
  { name = "urllib3" },
4840
  ]
4841
+ sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" }
4842
  wheels = [
4843
+ { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" },
4844
  ]
4845
 
4846
  [[package]]
 
5021
  { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" },
5022
  ]
5023
 
 
 
 
 
 
 
5024
  [[package]]
5025
  name = "shellingham"
5026
  version = "1.5.4"
 
5167
 
5168
  [[package]]
5169
  name = "starlette"
5170
+ version = "1.0.1"
5171
  source = { registry = "https://pypi.org/simple" }
5172
  dependencies = [
5173
  { name = "anyio" },
5174
  { name = "typing-extensions" },
5175
  ]
5176
+ sdist = { url = "https://files.pythonhosted.org/packages/08/a3/84e821cc54b4ab50ae6dbc6ac3800a651b65ec35f045cc73785380654057/starlette-1.0.1.tar.gz", hash = "sha256:512399c5f1de7fac99c88572212ded9ddeddef2fb32afa82d724000e88b38f4f", size = 2659596, upload-time = "2026-05-21T21:58:58.433Z" }
5177
  wheels = [
5178
+ { url = "https://files.pythonhosted.org/packages/ec/e1/b2df4bc09a1e51ff664c1e17018a4274b42e5e9352e4a478ea540512dc88/starlette-1.0.1-py3-none-any.whl", hash = "sha256:7c0e69b2ee1c848bd54669d908500117a3ee13de603a21427e5c6fc1adf98dcd", size = 72802, upload-time = "2026-05-21T21:58:56.551Z" },
5179
  ]
5180
 
5181
  [[package]]
5182
  name = "streamlit"
5183
+ version = "1.54.0"
5184
  source = { registry = "https://pypi.org/simple" }
5185
  dependencies = [
5186
  { name = "altair" },
 
5202
  { name = "typing-extensions" },
5203
  { name = "watchdog", marker = "sys_platform != 'darwin'" },
5204
  ]
5205
+ sdist = { url = "https://files.pythonhosted.org/packages/be/66/d887ee80ea85f035baee607c60af024994e17ae9b921277fca9675e76ecf/streamlit-1.54.0.tar.gz", hash = "sha256:09965e6ae7eb0357091725de1ce2a3f7e4be155c2464c505c40a3da77ab69dd8", size = 8662292, upload-time = "2026-02-04T16:37:54.734Z" }
5206
  wheels = [
5207
+ { url = "https://files.pythonhosted.org/packages/48/1d/40de1819374b4f0507411a60f4d2de0d620a9b10c817de5925799132b6c9/streamlit-1.54.0-py3-none-any.whl", hash = "sha256:a7b67d6293a9f5f6b4d4c7acdbc4980d7d9f049e78e404125022ecb1712f79fc", size = 9119730, upload-time = "2026-02-04T16:37:52.199Z" },
5208
  ]
5209
 
5210
  [[package]]
 
5380
 
5381
  [[package]]
5382
  name = "tornado"
5383
+ version = "6.5.5"
5384
  source = { registry = "https://pypi.org/simple" }
5385
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" }
5386
  wheels = [
5387
+ { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" },
5388
+ { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" },
5389
+ { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" },
5390
+ { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" },
5391
+ { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" },
5392
+ { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" },
5393
+ { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" },
5394
+ { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" },
5395
+ { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" },
 
 
5396
  ]
5397
 
5398
  [[package]]
 
5477
 
5478
  [[package]]
5479
  name = "transformers"
5480
+ version = "5.0.0rc3"
5481
  source = { registry = "https://pypi.org/simple" }
5482
  dependencies = [
5483
  { name = "filelock" },
 
5490
  { name = "safetensors" },
5491
  { name = "tokenizers" },
5492
  { name = "tqdm" },
5493
+ { name = "typer-slim" },
5494
  ]
5495
+ sdist = { url = "https://files.pythonhosted.org/packages/3f/a3/7c116a8d85f69ea7749cf4c2df79e64c35d028e5fc7ea0168f299d03b8c7/transformers-5.0.0rc3.tar.gz", hash = "sha256:a0315b92b7e087617ade42ec9e6e92ee7620541cc5d6a3331886c52cbe306f5c", size = 8388520, upload-time = "2026-01-14T16:49:02.952Z" }
5496
  wheels = [
5497
+ { url = "https://files.pythonhosted.org/packages/1e/f2/ae2b8968764253bdf38a48dee3c299b8d0bedf7c8ffbe3449fca9bd95338/transformers-5.0.0rc3-py3-none-any.whl", hash = "sha256:383fad27f4f73092d330e45fae384681e5c8521e1dc1cf6cb1a297780e68bf2d", size = 10107087, upload-time = "2026-01-14T16:48:59.393Z" },
5498
  ]
5499
 
5500
  [[package]]
 
5507
 
5508
  [[package]]
5509
  name = "typer"
5510
+ version = "0.25.1"
5511
  source = { registry = "https://pypi.org/simple" }
5512
  dependencies = [
5513
+ { name = "annotated-doc" },
5514
  { name = "click" },
5515
  { name = "rich" },
5516
  { name = "shellingham" },
 
5517
  ]
5518
+ sdist = { url = "https://files.pythonhosted.org/packages/e4/51/9aed62104cea109b820bbd6c14245af756112017d309da813ef107d42e7e/typer-0.25.1.tar.gz", hash = "sha256:9616eb8853a09ffeabab1698952f33c6f29ffdbceb4eaeecf571880e8d7664cc", size = 122276, upload-time = "2026-04-30T19:32:16.964Z" }
5519
  wheels = [
5520
+ { url = "https://files.pythonhosted.org/packages/3f/f9/2b3ff4e56e5fa7debfaf9eb135d0da96f3e9a1d5b27222223c7296336e5f/typer-0.25.1-py3-none-any.whl", hash = "sha256:75caa44ed46a03fb2dab8808753ffacdbfea88495e74c85a28c5eefcf5f39c89", size = 58409, upload-time = "2026-04-30T19:32:18.271Z" },
5521
+ ]
5522
+
5523
+ [[package]]
5524
+ name = "typer-slim"
5525
+ version = "0.24.0"
5526
+ source = { registry = "https://pypi.org/simple" }
5527
+ dependencies = [
5528
+ { name = "typer" },
5529
+ ]
5530
+ sdist = { url = "https://files.pythonhosted.org/packages/a7/a7/e6aecc4b4eb59598829a3b5076a93aff291b4fdaa2ded25efc4e1f4d219c/typer_slim-0.24.0.tar.gz", hash = "sha256:f0ed36127183f52ae6ced2ecb2521789995992c521a46083bfcdbb652d22ad34", size = 4776, upload-time = "2026-02-16T22:08:51.2Z" }
5531
+ wheels = [
5532
+ { url = "https://files.pythonhosted.org/packages/a7/24/5480c20380dfd18cf33d14784096dca45a24eae6102e91d49a718d3b6855/typer_slim-0.24.0-py3-none-any.whl", hash = "sha256:d5d7ee1ee2834d5020c7c616ed5e0d0f29b9a4b1dd283bdebae198ec09778d0e", size = 3394, upload-time = "2026-02-16T22:08:49.92Z" },
5533
  ]
5534
 
5535
  [[package]]
 
5756
 
5757
  [[package]]
5758
  name = "werkzeug"
5759
+ version = "3.1.6"
5760
  source = { registry = "https://pypi.org/simple" }
5761
  dependencies = [
5762
  { name = "markupsafe" },
5763
  ]
5764
+ sdist = { url = "https://files.pythonhosted.org/packages/61/f1/ee81806690a87dab5f5653c1f146c92bc066d7f4cebc603ef88eb9e13957/werkzeug-3.1.6.tar.gz", hash = "sha256:210c6bede5a420a913956b4791a7f4d6843a43b6fcee4dfa08a65e93007d0d25", size = 864736, upload-time = "2026-02-19T15:17:18.884Z" }
5765
  wheels = [
5766
+ { url = "https://files.pythonhosted.org/packages/4d/ec/d58832f89ede95652fd01f4f24236af7d32b70cab2196dfcc2d2fd13c5c2/werkzeug-3.1.6-py3-none-any.whl", hash = "sha256:7ddf3357bb9564e407607f988f683d72038551200c704012bb9a4c523d42f131", size = 225166, upload-time = "2026-02-19T15:17:17.475Z" },
5767
  ]
5768
 
5769
  [[package]]
 
5873
 
5874
  [[package]]
5875
  name = "yt-dlp"
5876
+ version = "2026.2.21"
5877
  source = { registry = "https://pypi.org/simple" }
5878
+ sdist = { url = "https://files.pythonhosted.org/packages/58/d9/55ffff25204733e94a507552ad984d5a8a8e4f9d1f0d91763e6b1a41c79b/yt_dlp-2026.2.21.tar.gz", hash = "sha256:4407dfc1a71fec0dee5ef916a8d4b66057812939b509ae45451fa8fb4376b539", size = 3116630, upload-time = "2026-02-21T20:40:53.522Z" }
5879
  wheels = [
5880
+ { url = "https://files.pythonhosted.org/packages/5a/40/664c99ee36d80d84ce7a96cd98aebcb3d16c19e6c3ad3461d2cf5424040e/yt_dlp-2026.2.21-py3-none-any.whl", hash = "sha256:0d8408f5b6d20487f5caeb946dfd04f9bcd2f1a3a125b744a0a982b590e449f7", size = 3313392, upload-time = "2026-02-21T20:40:51.514Z" },
5881
  ]
5882
 
5883
  [[package]]