diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..c7b009e446fb177a8e9d177d8944d03707d0d101
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,30 @@
+FROM python:3.11-slim
+
+# Install system deps
+RUN apt-get update && apt-get install -y \
+ git curl build-essential libssl-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+WORKDIR /app
+
+# Install Python deps
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copy source
+COPY . .
+
+# Create workspace
+RUN mkdir -p /tmp/workspace /tmp/repos
+
+# HuggingFace Spaces runs as user 1000
+RUN useradd -m -u 1000 user && chown -R user:user /app /tmp/workspace /tmp/repos
+USER 1000
+
+EXPOSE 7860
+
+ENV PORT=7860
+ENV HOST=0.0.0.0
+ENV DB_PATH=/tmp/devin_agent.db
+
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860", "--workers", "1", "--loop", "asyncio"]
diff --git a/backend/Dockerfile.hf b/backend/Dockerfile.hf
new file mode 100644
index 0000000000000000000000000000000000000000..c1c88d894a4559758feb5f87f1b540a89324ff8f
--- /dev/null
+++ b/backend/Dockerfile.hf
@@ -0,0 +1,48 @@
+FROM python:3.11-slim
+
+# HuggingFace Spaces Dockerfile
+# Compatible with free CPU tier
+
+WORKDIR /app
+
+# System deps
+RUN apt-get update && apt-get install -y \
+ git curl build-essential \
+ && rm -rf /var/lib/apt/lists/*
+
+# Python deps
+COPY requirements.txt .
+RUN pip install --no-cache-dir --upgrade pip && \
+ pip install --no-cache-dir -r requirements.txt
+
+# App code
+COPY . .
+
+# Setup dirs
+RUN mkdir -p /tmp/workspace /tmp/repos /tmp/devin_data
+
+# HF runs as uid 1000
+RUN useradd -m -u 1000 user 2>/dev/null || true
+RUN chown -R 1000:1000 /app /tmp/workspace /tmp/repos /tmp/devin_data
+
+USER 1000
+
+EXPOSE 7860
+
+ENV PORT=7860
+ENV HOST=0.0.0.0
+ENV DB_PATH=/tmp/devin_agent.db
+ENV PYTHONUNBUFFERED=1
+ENV PYTHONDONTWRITEBYTECODE=1
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
+ CMD curl -f http://localhost:7860/api/v1/health || exit 1
+
+CMD ["uvicorn", "main:app", \
+ "--host", "0.0.0.0", \
+ "--port", "7860", \
+ "--workers", "1", \
+ "--loop", "asyncio", \
+ "--timeout-keep-alive", "75", \
+ "--log-level", "info"]
diff --git a/backend/README.md b/backend/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..8d5b15bc9685fe2c43f49bfbda07c63f91613ef3
--- /dev/null
+++ b/backend/README.md
@@ -0,0 +1,58 @@
+---
+title: Devin Agent Platform
+emoji: π€
+colorFrom: blue
+colorTo: purple
+sdk: docker
+app_port: 7860
+pinned: true
+license: mit
+short_description: Production-grade autonomous AI engineering platform
+---
+
+# π€ Devin Agent Platform v2.0
+
+> **Manus/Devin-style Autonomous AI Engineering Platform**
+> Real-time WebSocket streaming Β· Autonomous GitHub operations Β· Persistent memory
+
+## β¨ Features
+
+- β‘ **Real-time WebSocket streaming** β live token-by-token LLM output
+- πΊοΈ **Autonomous task planning** β goal β plan β execute automatically
+- π§ **Persistent memory** β SQLite-backed conversation + project memory
+- π **GitHub automation** β clone, commit, push, PR, issues autonomously
+- π **Self-healing** β auto-retry with exponential backoff
+- π‘ **SSE fallback** β Server-Sent Events for streaming compatibility
+- π **REST + WebSocket API** β full-featured backend
+
+## π API Endpoints
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| POST | `/api/v1/tasks/create` | Create autonomous task |
+| GET | `/api/v1/tasks/{id}` | Get task details |
+| POST | `/api/v1/tasks/{id}/cancel` | Cancel task |
+| POST | `/api/v1/tasks/{id}/retry` | Retry failed task |
+| GET | `/api/v1/tasks/{id}/stream` | SSE task stream |
+| POST | `/api/v1/chat` | Chat with agent |
+| POST | `/api/v1/goal` | Submit high-level goal |
+| POST | `/api/v1/plan` | Generate execution plan |
+| WS | `/ws/tasks/{task_id}` | Live task WebSocket |
+| WS | `/ws/logs` | Global log stream |
+| WS | `/ws/chat/{session_id}` | Chat WebSocket |
+| WS | `/ws/agent/status` | Agent status stream |
+
+## π Environment Variables (HF Secrets)
+
+```
+OPENAI_API_KEY = sk-... (for real AI)
+ANTHROPIC_API_KEY = sk-ant-... (alternative)
+GITHUB_TOKEN = ghp_... (GitHub ops)
+GITHUB_OWNER = your-username (GitHub ops)
+```
+
+## π Quick Start
+
+Visit `/api/docs` for interactive Swagger UI.
+
+**Demo mode** works without any API keys β set `OPENAI_API_KEY` for real AI.
diff --git a/backend/__pycache__/main.cpython-312.pyc b/backend/__pycache__/main.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0a6043f0154f0fc529cbf6cd350edd46c5c0a266
Binary files /dev/null and b/backend/__pycache__/main.cpython-312.pyc differ
diff --git a/backend/api/__init__.py b/backend/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/api/__pycache__/__init__.cpython-312.pyc b/backend/api/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..56c0c66ef7122cd03c986d89e42750d26fcb0ab6
Binary files /dev/null and b/backend/api/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/api/__pycache__/websocket_manager.cpython-312.pyc b/backend/api/__pycache__/websocket_manager.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..39fe470d2d47dfadd2d4e08f78c620aefad89ac6
Binary files /dev/null and b/backend/api/__pycache__/websocket_manager.cpython-312.pyc differ
diff --git a/backend/api/routes/__init__.py b/backend/api/routes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/api/routes/__pycache__/__init__.cpython-312.pyc b/backend/api/routes/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5ccc91e45f72dfcd838292572a03de91995f0383
Binary files /dev/null and b/backend/api/routes/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/api/routes/__pycache__/chat.cpython-312.pyc b/backend/api/routes/__pycache__/chat.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..59bb359f9f22fbfe51c7b297cca6ac42c1c78254
Binary files /dev/null and b/backend/api/routes/__pycache__/chat.cpython-312.pyc differ
diff --git a/backend/api/routes/__pycache__/github.cpython-312.pyc b/backend/api/routes/__pycache__/github.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..32bc13856652ed46d33b9497e7325e31674d549a
Binary files /dev/null and b/backend/api/routes/__pycache__/github.cpython-312.pyc differ
diff --git a/backend/api/routes/__pycache__/health.cpython-312.pyc b/backend/api/routes/__pycache__/health.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7b5fcc886a5431427c10b62efaba98c9c2c17db1
Binary files /dev/null and b/backend/api/routes/__pycache__/health.cpython-312.pyc differ
diff --git a/backend/api/routes/__pycache__/memory.cpython-312.pyc b/backend/api/routes/__pycache__/memory.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..82cf4b4d288bba84b1bed52730e51b20b2f738d1
Binary files /dev/null and b/backend/api/routes/__pycache__/memory.cpython-312.pyc differ
diff --git a/backend/api/routes/__pycache__/tasks.cpython-312.pyc b/backend/api/routes/__pycache__/tasks.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..93b690aa5eac9b4a354e0d9a727de72c5393c1f9
Binary files /dev/null and b/backend/api/routes/__pycache__/tasks.cpython-312.pyc differ
diff --git a/backend/api/routes/chat.py b/backend/api/routes/chat.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f46ee598bbdbb3aa29f17ed6ba8147054c9b397
--- /dev/null
+++ b/backend/api/routes/chat.py
@@ -0,0 +1,214 @@
+"""
+Chat + Goal API Routes β Real-time streaming responses
+"""
+
+import asyncio
+import json
+import time
+import uuid
+
+from fastapi import APIRouter, HTTPException, Request
+from fastapi.responses import StreamingResponse
+
+from core.models import ChatRequest, GoalRequest, TaskCreateRequest
+from memory.db import save_memory, get_history
+
+router = APIRouter()
+
+
+def get_engine(request: Request):
+ return request.app.state.task_engine
+
+
+def get_ws(request: Request):
+ return request.app.state.ws_manager
+
+
+# βββ Chat (REST + SSE streaming) βββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/chat", summary="Chat with the agent")
+async def chat(req: ChatRequest, request: Request):
+ from core.agent import AgentCore
+ ws = get_ws(request)
+ agent = AgentCore(ws)
+
+ messages = [{"role": m.role, "content": m.content} for m in req.messages]
+
+ if req.stream:
+ async def stream_gen():
+ async def _run():
+ result = await agent.llm_stream(
+ messages=messages,
+ session_id=req.session_id,
+ model=req.model,
+ temperature=req.temperature,
+ max_tokens=req.max_tokens,
+ )
+ await save_memory(
+ content=result,
+ memory_type="conversation",
+ session_id=req.session_id,
+ project_id=req.project_id,
+ key="assistant",
+ )
+ # Save user message too
+ user_msg = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "")
+ await save_memory(
+ content=user_msg,
+ memory_type="conversation",
+ session_id=req.session_id,
+ project_id=req.project_id,
+ key="user",
+ )
+ return result
+
+ room_buffer = []
+ original_emit_chat = ws.emit_chat
+ async def capture_emit(sid, etype, data):
+ if etype == "llm_chunk":
+ chunk = data.get("chunk", "")
+ room_buffer.append(chunk)
+ yield_data = json.dumps({"type": etype, "data": data, "session_id": sid})
+ return yield_data
+ return None
+
+ # Stream tokens directly
+ full = ""
+ from core.agent import AgentCore as _A
+ import httpx
+ import os
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
+ ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
+
+ if OPENAI_API_KEY:
+ headers = {
+ "Authorization": f"Bearer {OPENAI_API_KEY}",
+ "Content-Type": "application/json",
+ }
+ payload = {
+ "model": req.model,
+ "messages": messages,
+ "stream": True,
+ "temperature": req.temperature,
+ "max_tokens": req.max_tokens,
+ }
+ from core.agent import OPENAI_BASE_URL
+ async with httpx.AsyncClient(timeout=120) as client:
+ async with client.stream("POST", f"{OPENAI_BASE_URL}/chat/completions",
+ headers=headers, json=payload) as resp:
+ async for line in resp.aiter_lines():
+ if not line.startswith("data:"):
+ continue
+ chunk_str = line[6:].strip()
+ if chunk_str == "[DONE]":
+ break
+ try:
+ data = json.loads(chunk_str)
+ delta = data["choices"][0]["delta"].get("content", "")
+ if delta:
+ full += delta
+ yield f"data: {json.dumps({'type': 'llm_chunk', 'data': {'chunk': delta}, 'session_id': req.session_id})}\n\n"
+ except Exception:
+ pass
+ else:
+ # Demo streaming
+ demo = (
+ f"Hello! I'm your Devin-style AI Agent. I received: '{req.messages[-1].content[:80]}'. "
+ f"Set OPENAI_API_KEY or ANTHROPIC_API_KEY for real AI responses. "
+ f"I support real-time streaming, task planning, GitHub automation, and more!"
+ )
+ for word in demo.split():
+ chunk = word + " "
+ full += chunk
+ await asyncio.sleep(0.04)
+ yield f"data: {json.dumps({'type': 'llm_chunk', 'data': {'chunk': chunk}, 'session_id': req.session_id})}\n\n"
+
+ yield f"data: {json.dumps({'type': 'stream_end', 'data': {'full_response': full}, 'session_id': req.session_id})}\n\n"
+
+ return StreamingResponse(
+ stream_gen(),
+ media_type="text/event-stream",
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
+ )
+ else:
+ # Non-streaming
+ agent = AgentCore(get_ws(request))
+ result = await agent.llm_stream(messages, session_id=req.session_id)
+ return {
+ "response": result,
+ "session_id": req.session_id,
+ "model": req.model,
+ "timestamp": time.time(),
+ }
+
+
+@router.post("/chat/stream", summary="Explicit streaming chat endpoint")
+async def chat_stream(req: ChatRequest, request: Request):
+ req.stream = True
+ return await chat(req, request)
+
+
+# βββ Goal API (create task from goal) βββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/goal", summary="Submit a high-level goal to the agent")
+async def submit_goal(req: GoalRequest, request: Request):
+ engine = get_engine(request)
+ task_req = TaskCreateRequest(
+ goal=req.goal,
+ session_id=req.session_id,
+ project_id=req.project_id,
+ stream=req.stream,
+ metadata={"source": "goal_api", "github_repo": req.github_repo},
+ )
+ task_id = await engine.submit(task_req)
+ return {
+ "task_id": task_id,
+ "goal": req.goal,
+ "status": "queued",
+ "session_id": req.session_id,
+ "ws_url": f"/ws/tasks/{task_id}",
+ "stream_url": f"/api/v1/tasks/{task_id}/stream",
+ }
+
+
+@router.post("/goal/stream", summary="Submit goal with SSE streaming response")
+async def submit_goal_stream(req: GoalRequest, request: Request):
+ req.stream = True
+ return await submit_goal(req, request)
+
+
+# βββ Execute (direct tool execution) ββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/execute", summary="Execute a tool directly")
+async def execute(
+ tool: str,
+ task: str,
+ request: Request,
+ session_id: str = "",
+):
+ from tools.executor import ToolExecutor
+ ws = get_ws(request)
+ executor = ToolExecutor(ws)
+ result = await executor.run(
+ tool=tool,
+ task=task,
+ session_id=session_id,
+ )
+ return {"tool": tool, "task": task, "result": result, "session_id": session_id}
+
+
+# βββ Plan (generate plan without executing) βββββββββββββββββββββββββββββββββββ
+
+@router.post("/plan", summary="Generate execution plan for a goal")
+async def generate_plan(req: GoalRequest, request: Request):
+ from core.agent import AgentCore
+ ws = get_ws(request)
+ agent = AgentCore(ws)
+ task_id = f"plan_{uuid.uuid4().hex[:8]}"
+ plan = await agent.plan(goal=req.goal, task_id=task_id, session_id=req.session_id)
+ return {
+ "goal": req.goal,
+ "plan": plan.model_dump(),
+ "session_id": req.session_id,
+ "task_id": task_id,
+ }
diff --git a/backend/api/routes/github.py b/backend/api/routes/github.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6dc31c778141f8b1633e2ea90254827770b17d3
--- /dev/null
+++ b/backend/api/routes/github.py
@@ -0,0 +1,336 @@
+"""
+GitHub Autonomous Engineering API Routes
+Clone, commit, push, PR, issues β all autonomous
+"""
+
+import os
+import time
+import asyncio
+import tempfile
+import shutil
+from typing import Optional
+
+import httpx
+from fastapi import APIRouter, HTTPException, Request
+
+from core.models import (
+ GitHubCloneRequest, GitHubCreateRepoRequest,
+ GitHubCommitRequest, GitHubPRRequest, GitHubIssueRequest,
+)
+from memory.db import save_memory
+
+router = APIRouter()
+
+GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "")
+GITHUB_OWNER = os.environ.get("GITHUB_OWNER", "")
+GITHUB_API = "https://api.github.com"
+
+
+def gh_headers():
+ if not GITHUB_TOKEN:
+ raise HTTPException(status_code=400, detail="GITHUB_TOKEN not configured")
+ return {
+ "Authorization": f"Bearer {GITHUB_TOKEN}",
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28",
+ }
+
+
+async def gh_get(path: str) -> dict:
+ async with httpx.AsyncClient(timeout=30) as client:
+ r = await client.get(f"{GITHUB_API}{path}", headers=gh_headers())
+ r.raise_for_status()
+ return r.json()
+
+
+async def gh_post(path: str, data: dict) -> dict:
+ async with httpx.AsyncClient(timeout=30) as client:
+ r = await client.post(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
+ r.raise_for_status()
+ return r.json()
+
+
+async def gh_put(path: str, data: dict) -> dict:
+ async with httpx.AsyncClient(timeout=30) as client:
+ r = await client.put(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
+ r.raise_for_status()
+ return r.json()
+
+
+async def gh_patch(path: str, data: dict) -> dict:
+ async with httpx.AsyncClient(timeout=30) as client:
+ r = await client.patch(f"{GITHUB_API}{path}", headers=gh_headers(), json=data)
+ r.raise_for_status()
+ return r.json()
+
+
+# βββ Clone ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/clone", summary="Clone a GitHub repository")
+async def clone_repo(req: GitHubCloneRequest):
+ try:
+ import git
+ except ImportError:
+ raise HTTPException(status_code=500, detail="gitpython not installed")
+
+ local_path = req.local_path or f"/tmp/repos/{req.repo_url.split('/')[-1].replace('.git', '')}"
+ os.makedirs(local_path, exist_ok=True)
+
+ if GITHUB_TOKEN:
+ url = req.repo_url.replace("https://", f"https://{GITHUB_TOKEN}@")
+ else:
+ url = req.repo_url
+
+ try:
+ if os.path.exists(os.path.join(local_path, ".git")):
+ repo = git.Repo(local_path)
+ repo.remotes.origin.pull()
+ action = "pulled"
+ else:
+ repo = git.Repo.clone_from(url, local_path, branch=req.branch, depth=1)
+ action = "cloned"
+
+ files = []
+ for root, dirs, fnames in os.walk(local_path):
+ dirs[:] = [d for d in dirs if d not in [".git", "node_modules", "__pycache__"]]
+ for f in fnames[:50]:
+ files.append(os.path.relpath(os.path.join(root, f), local_path))
+
+ # Save to memory
+ await save_memory(
+ content=f"Repo {req.repo_url} cloned to {local_path}. Files: {', '.join(files[:20])}",
+ memory_type="repo",
+ key=req.repo_url,
+ )
+
+ return {
+ "action": action,
+ "repo_url": req.repo_url,
+ "local_path": local_path,
+ "branch": req.branch,
+ "files_count": len(files),
+ "files": files[:30],
+ }
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Clone failed: {str(e)}")
+
+
+# βββ Create Repo ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/create_repo", summary="Create a new GitHub repository")
+async def create_repo(req: GitHubCreateRepoRequest):
+ data = {
+ "name": req.name,
+ "description": req.description,
+ "private": req.private,
+ "auto_init": req.auto_init,
+ }
+ try:
+ result = await gh_post("/user/repos", data)
+ return {
+ "repo": result["full_name"],
+ "url": result["html_url"],
+ "clone_url": result["clone_url"],
+ "default_branch": result.get("default_branch", "main"),
+ "private": result["private"],
+ }
+ except httpx.HTTPStatusError as e:
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
+
+
+# βββ Commit Files βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/commit", summary="Commit files to a repository")
+async def commit_files(req: GitHubCommitRequest):
+ import base64
+
+ owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
+ results = []
+
+ for file_path, content in req.files.items():
+ encoded = base64.b64encode(content.encode()).decode()
+
+ # Get current SHA if file exists
+ sha = None
+ try:
+ existing = await gh_get(f"/repos/{owner_repo}/contents/{file_path}?ref={req.branch}")
+ sha = existing.get("sha")
+ except Exception:
+ pass
+
+ payload = {
+ "message": req.message,
+ "content": encoded,
+ "branch": req.branch,
+ }
+ if sha:
+ payload["sha"] = sha
+
+ try:
+ result = await gh_put(f"/repos/{owner_repo}/contents/{file_path}", payload)
+ results.append({"file": file_path, "status": "committed", "sha": result["content"]["sha"]})
+ except Exception as e:
+ results.append({"file": file_path, "status": "error", "error": str(e)})
+
+ return {
+ "repo": owner_repo,
+ "branch": req.branch,
+ "message": req.message,
+ "files": results,
+ "committed": sum(1 for r in results if r["status"] == "committed"),
+ }
+
+
+# βββ Push βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/push", summary="Push local changes to remote")
+async def push_changes(
+ repo_path: str,
+ branch: str = "main",
+ message: str = "Auto-commit by Devin Agent",
+):
+ try:
+ import git
+ repo = git.Repo(repo_path)
+ repo.git.add(A=True)
+ if repo.index.diff("HEAD") or repo.untracked_files:
+ repo.index.commit(message)
+ origin = repo.remote("origin")
+ origin.push(refspec=f"HEAD:{branch}")
+ return {"status": "pushed", "branch": branch, "message": message}
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Push failed: {str(e)}")
+
+
+# βββ Create PR ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/pr/create", summary="Create a Pull Request")
+async def create_pr(req: GitHubPRRequest):
+ owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
+ data = {
+ "title": req.title,
+ "body": req.body,
+ "head": req.head,
+ "base": req.base,
+ "draft": req.draft,
+ }
+ try:
+ result = await gh_post(f"/repos/{owner_repo}/pulls", data)
+ return {
+ "pr_number": result["number"],
+ "title": result["title"],
+ "url": result["html_url"],
+ "state": result["state"],
+ "head": req.head,
+ "base": req.base,
+ }
+ except httpx.HTTPStatusError as e:
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
+
+
+# βββ Create Issue βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/issues/create", summary="Create a GitHub Issue")
+async def create_issue(req: GitHubIssueRequest):
+ owner_repo = req.repo if "/" in req.repo else f"{GITHUB_OWNER}/{req.repo}"
+ data = {"title": req.title, "body": req.body, "labels": req.labels}
+ try:
+ result = await gh_post(f"/repos/{owner_repo}/issues", data)
+ return {
+ "issue_number": result["number"],
+ "title": result["title"],
+ "url": result["html_url"],
+ "state": result["state"],
+ }
+ except httpx.HTTPStatusError as e:
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
+
+
+# βββ Code Review ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/review", summary="AI code review for a PR")
+async def review_pr(repo: str, pr_number: int, request: Request):
+ owner_repo = repo if "/" in repo else f"{GITHUB_OWNER}/{repo}"
+ try:
+ pr = await gh_get(f"/repos/{owner_repo}/pulls/{pr_number}")
+ files = await gh_get(f"/repos/{owner_repo}/pulls/{pr_number}/files")
+
+ file_changes = []
+ for f in files[:10]:
+ file_changes.append(f"{f['filename']}: +{f.get('additions',0)}/-{f.get('deletions',0)}")
+
+ ws = request.app.state.ws_manager
+ from core.agent import AgentCore
+ agent = AgentCore(ws)
+
+ review_prompt = (
+ f"Review this Pull Request:\n"
+ f"Title: {pr['title']}\n"
+ f"Description: {pr.get('body', 'No description')}\n"
+ f"Files changed: {chr(10).join(file_changes)}\n\n"
+ f"Provide a constructive code review with: summary, potential issues, suggestions, and verdict."
+ )
+ messages = [
+ {"role": "system", "content": "You are a senior software engineer doing code review. Be constructive, specific, and helpful."},
+ {"role": "user", "content": review_prompt},
+ ]
+ review = await agent.llm_stream(messages)
+
+ # Post review comment
+ if GITHUB_TOKEN:
+ await gh_post(f"/repos/{owner_repo}/issues/{pr_number}/comments", {"body": f"π€ **Devin Agent Code Review**\n\n{review}"})
+
+ return {
+ "pr_number": pr_number,
+ "title": pr["title"],
+ "review": review,
+ "files_reviewed": len(files),
+ "posted_to_github": bool(GITHUB_TOKEN),
+ }
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+
+# βββ Repo Info ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.get("/repo/{owner}/{repo}", summary="Get repository info")
+async def get_repo_info(owner: str, repo: str):
+ try:
+ info = await gh_get(f"/repos/{owner}/{repo}")
+ return {
+ "name": info["name"],
+ "full_name": info["full_name"],
+ "description": info.get("description"),
+ "url": info["html_url"],
+ "default_branch": info["default_branch"],
+ "language": info.get("language"),
+ "stars": info["stargazers_count"],
+ "forks": info["forks_count"],
+ "open_issues": info["open_issues_count"],
+ "private": info["private"],
+ }
+ except httpx.HTTPStatusError as e:
+ raise HTTPException(status_code=e.response.status_code, detail=e.response.text)
+
+
+# βββ Status check βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.get("/status", summary="GitHub integration status")
+async def github_status():
+ configured = bool(GITHUB_TOKEN)
+ user = None
+ if configured:
+ try:
+ user_info = await gh_get("/user")
+ user = user_info.get("login")
+ except Exception:
+ configured = False
+ return {
+ "configured": configured,
+ "user": user,
+ "owner": GITHUB_OWNER or user,
+ "capabilities": [
+ "clone", "create_repo", "commit", "push",
+ "pr/create", "issues/create", "review"
+ ],
+ }
diff --git a/backend/api/routes/health.py b/backend/api/routes/health.py
new file mode 100644
index 0000000000000000000000000000000000000000..23f54e1467b868ea8deb41de78d6ea1c497a8cd0
--- /dev/null
+++ b/backend/api/routes/health.py
@@ -0,0 +1,53 @@
+"""
+Health + Status Routes
+"""
+
+import time
+import os
+import psutil
+from fastapi import APIRouter, Request
+
+router = APIRouter()
+
+
+@router.get("/health", summary="Health check")
+async def health(request: Request):
+ ws = request.app.state.ws_manager
+ engine = request.app.state.task_engine
+ stats = ws.get_stats()
+ return {
+ "status": "healthy",
+ "version": "2.0.0",
+ "timestamp": time.time(),
+ "websocket_connections": stats["total_connections"],
+ "websocket_rooms": list(stats["rooms"].keys()),
+ "task_queue_size": engine._queue.qsize(),
+ "active_tasks": len(engine._active),
+ "llm": {
+ "openai": bool(os.environ.get("OPENAI_API_KEY")),
+ "anthropic": bool(os.environ.get("ANTHROPIC_API_KEY")),
+ "model": os.environ.get("DEFAULT_MODEL", "gpt-4o"),
+ },
+ "github": bool(os.environ.get("GITHUB_TOKEN")),
+ }
+
+
+@router.get("/metrics", summary="System metrics")
+async def metrics():
+ cpu = psutil.cpu_percent(interval=0.1)
+ mem = psutil.virtual_memory()
+ disk = psutil.disk_usage("/")
+ return {
+ "cpu_percent": cpu,
+ "memory": {
+ "total_mb": round(mem.total / 1024 / 1024),
+ "used_mb": round(mem.used / 1024 / 1024),
+ "percent": mem.percent,
+ },
+ "disk": {
+ "total_gb": round(disk.total / 1024 / 1024 / 1024, 1),
+ "used_gb": round(disk.used / 1024 / 1024 / 1024, 1),
+ "percent": disk.percent,
+ },
+ "timestamp": time.time(),
+ }
diff --git a/backend/api/routes/memory.py b/backend/api/routes/memory.py
new file mode 100644
index 0000000000000000000000000000000000000000..52f485689944fb2bca0f71f4b31172da3176503d
--- /dev/null
+++ b/backend/api/routes/memory.py
@@ -0,0 +1,50 @@
+"""
+Memory API Routes β Persistent agent memory
+"""
+
+import time
+from fastapi import APIRouter, HTTPException, Query
+from core.models import MemorySaveRequest, MemorySearchRequest
+from memory.db import save_memory, search_memory, get_project_memory, get_history
+
+router = APIRouter()
+
+
+@router.post("/", summary="Save memory")
+async def save(req: MemorySaveRequest):
+ await save_memory(
+ content=req.content,
+ memory_type=req.memory_type.value,
+ session_id=req.session_id,
+ project_id=req.project_id,
+ key=req.key,
+ metadata=req.metadata,
+ )
+ return {"status": "saved", "memory_type": req.memory_type, "timestamp": time.time()}
+
+
+@router.post("/search", summary="Search memory")
+async def search(req: MemorySearchRequest):
+ results = await search_memory(
+ query=req.query,
+ session_id=req.session_id,
+ project_id=req.project_id,
+ limit=req.limit,
+ )
+ return {"results": results, "total": len(results), "query": req.query}
+
+
+@router.get("/project/{project_id}", summary="Get project memory")
+async def project_memory(
+ project_id: str,
+ memory_type: str = Query(default=""),
+ limit: int = Query(default=100, le=500),
+):
+ results = await get_project_memory(project_id, memory_type=memory_type, limit=limit)
+ return {"project_id": project_id, "memories": results, "total": len(results)}
+
+
+@router.get("/history/{session_id}", summary="Get conversation history")
+async def history(session_id: str, limit: int = Query(default=50, le=200)):
+ results = await get_history(session_id, limit=limit)
+ return {"session_id": session_id, "history": results, "total": len(results)}
diff --git a/backend/api/routes/tasks.py b/backend/api/routes/tasks.py
new file mode 100644
index 0000000000000000000000000000000000000000..82fde607690b1a939594ec278ff6191888af1e63
--- /dev/null
+++ b/backend/api/routes/tasks.py
@@ -0,0 +1,167 @@
+"""
+Task API Routes β CRUD + Streaming + WebSocket
+"""
+
+import asyncio
+import json
+import time
+from typing import Optional
+
+from fastapi import APIRouter, HTTPException, Request, Query
+from fastapi.responses import StreamingResponse
+
+from core.models import (
+ TaskCreateRequest, TaskCancelRequest, TaskRetryRequest, TaskResponse, TaskStatus
+)
+from memory.db import get_task, list_tasks, get_task_events, update_task_status
+
+router = APIRouter()
+
+
+def get_engine(request: Request):
+ return request.app.state.task_engine
+
+
+def get_ws(request: Request):
+ return request.app.state.ws_manager
+
+
+# βββ Create Task βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/create", summary="Create & queue a new agent task")
+async def create_task(req: TaskCreateRequest, request: Request):
+ engine = get_engine(request)
+ task_id = await engine.submit(req)
+ task = await get_task(task_id)
+ return {
+ "task_id": task_id,
+ "status": "queued",
+ "goal": req.goal,
+ "session_id": req.session_id,
+ "stream_url": f"/api/v1/tasks/{task_id}/stream",
+ "ws_url": f"/ws/tasks/{task_id}",
+ "created_at": task["created_at"] if task else time.time(),
+ }
+
+
+# βββ Get Task ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.get("/{task_id}", summary="Get task details")
+async def get_task_detail(task_id: str):
+ task = await get_task(task_id)
+ if not task:
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
+ return task
+
+
+# βββ Get Task Status βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.get("/{task_id}/status", summary="Get task status only")
+async def get_task_status(task_id: str):
+ task = await get_task(task_id)
+ if not task:
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
+ return {
+ "task_id": task_id,
+ "status": task["status"],
+ "retry_count": task.get("retry_count", 0),
+ "created_at": task.get("created_at"),
+ "started_at": task.get("started_at"),
+ "completed_at": task.get("completed_at"),
+ }
+
+
+# βββ Cancel Task βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/{task_id}/cancel", summary="Cancel a running task")
+async def cancel_task(task_id: str, req: TaskCancelRequest, request: Request):
+ task = await get_task(task_id)
+ if not task:
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
+ if task["status"] in ("completed", "failed", "cancelled"):
+ raise HTTPException(status_code=400, detail=f"Task already {task['status']}")
+ engine = get_engine(request)
+ await engine.cancel(task_id, req.reason)
+ return {"task_id": task_id, "status": "cancelled", "reason": req.reason}
+
+
+# βββ Retry Task ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.post("/{task_id}/retry", summary="Retry a failed task")
+async def retry_task(task_id: str, request: Request):
+ task = await get_task(task_id)
+ if not task:
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
+ if task["status"] not in ("failed", "cancelled"):
+ raise HTTPException(status_code=400, detail="Only failed/cancelled tasks can be retried")
+ engine = get_engine(request)
+ await engine.retry(task_id)
+ return {"task_id": task_id, "status": "queued", "message": "Task requeued for retry"}
+
+
+# βββ Stream Task Events (SSE) ββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.get("/{task_id}/stream", summary="Stream task events via SSE")
+async def stream_task(task_id: str, request: Request):
+ task = await get_task(task_id)
+ if not task:
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
+
+ async def event_generator():
+ # First, replay all stored events
+ events = await get_task_events(task_id)
+ for ev in events:
+ data = json.dumps({
+ "type": ev["event_type"],
+ "task_id": task_id,
+ "timestamp": ev["timestamp"],
+ "data": json.loads(ev["data"]) if ev.get("data") else {},
+ })
+ yield f"data: {data}\n\n"
+
+ # Then stream live events via WS manager buffer
+ ws = get_ws(request)
+ room = f"task:{task_id}"
+ last_count = len(events)
+
+ # Poll for new events (for SSE fallback)
+ for _ in range(600): # max 5 minutes
+ await asyncio.sleep(0.5)
+ current_task = await get_task(task_id)
+ if current_task and current_task["status"] in ("completed", "failed", "cancelled"):
+ yield f"data: {json.dumps({'type': 'stream_end', 'task_id': task_id, 'status': current_task['status']})}\n\n"
+ break
+ # heartbeat
+ yield f"data: {json.dumps({'type': 'heartbeat', 'timestamp': time.time()})}\n\n"
+
+ return StreamingResponse(
+ event_generator(),
+ media_type="text/event-stream",
+ headers={
+ "Cache-Control": "no-cache",
+ "X-Accel-Buffering": "no",
+ "Connection": "keep-alive",
+ },
+ )
+
+
+# βββ List Tasks ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.get("/", summary="List tasks")
+async def list_all_tasks(
+ session_id: str = Query(default=""),
+ limit: int = Query(default=50, le=200),
+):
+ tasks = await list_tasks(session_id=session_id, limit=limit)
+ return {"tasks": tasks, "total": len(tasks)}
+
+
+# βββ Task Events History βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+@router.get("/{task_id}/events", summary="Get all events for a task")
+async def task_events(task_id: str):
+ task = await get_task(task_id)
+ if not task:
+ raise HTTPException(status_code=404, detail=f"Task {task_id} not found")
+ events = await get_task_events(task_id)
+ return {"task_id": task_id, "events": events, "total": len(events)}
diff --git a/backend/api/websocket_manager.py b/backend/api/websocket_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..68aea8a58e3d2381e6eec51197b6a9f13ad9931d
--- /dev/null
+++ b/backend/api/websocket_manager.py
@@ -0,0 +1,134 @@
+"""
+WebSocket Connection Manager β Production Grade
+Handles rooms, heartbeats, event buffering, reconnect support
+"""
+
+import asyncio
+import json
+import time
+import uuid
+from collections import defaultdict
+from typing import Dict, List, Optional, Set
+import structlog
+
+log = structlog.get_logger()
+
+
+class WebSocketManager:
+ def __init__(self):
+ # room β set of websockets
+ self._rooms: Dict[str, Set] = defaultdict(set)
+ # ws β list of rooms
+ self._ws_rooms: Dict[object, Set[str]] = defaultdict(set)
+ # Event buffer per room (for replay on reconnect)
+ self._event_buffer: Dict[str, List] = defaultdict(list)
+ self._buffer_max = 100
+ # Active connection count
+ self._connection_count = 0
+
+ async def connect(self, websocket, room: str):
+ await websocket.accept()
+ self._rooms[room].add(websocket)
+ self._ws_rooms[websocket].add(room)
+ self._connection_count += 1
+ log.info("WS connected", room=room, total=self._connection_count)
+
+ # Replay buffered events for this room
+ buffered = self._event_buffer.get(room, [])[-20:]
+ for event in buffered:
+ try:
+ await websocket.send_json(event)
+ except Exception:
+ pass
+
+ await websocket.send_json({
+ "type": "connected",
+ "room": room,
+ "timestamp": time.time(),
+ "buffered_events": len(buffered),
+ })
+
+ def disconnect(self, websocket, room: Optional[str] = None):
+ if room:
+ self._rooms[room].discard(websocket)
+ self._ws_rooms[websocket].discard(room)
+ else:
+ for r in list(self._ws_rooms.get(websocket, [])):
+ self._rooms[r].discard(websocket)
+ self._ws_rooms.pop(websocket, None)
+ self._connection_count = max(0, self._connection_count - 1)
+ log.info("WS disconnected", room=room, total=self._connection_count)
+
+ async def broadcast(self, room: str, event: dict):
+ """Broadcast event to all sockets in a room."""
+ if "timestamp" not in event:
+ event["timestamp"] = time.time()
+ if "id" not in event:
+ event["id"] = str(uuid.uuid4())[:8]
+
+ # Buffer event
+ self._event_buffer[room].append(event)
+ if len(self._event_buffer[room]) > self._buffer_max:
+ self._event_buffer[room].pop(0)
+
+ dead = set()
+ for ws in list(self._rooms.get(room, [])):
+ try:
+ await ws.send_json(event)
+ except Exception:
+ dead.add(ws)
+
+ for ws in dead:
+ self.disconnect(ws, room)
+
+ async def broadcast_global(self, event: dict):
+ """Broadcast to ALL connected websockets."""
+ for room in list(self._rooms.keys()):
+ await self.broadcast(room, event)
+
+ async def emit(self, task_id: str, event_type: str, data: dict, session_id: str = ""):
+ """Emit a structured event to a task room + logs room."""
+ event = {
+ "type": event_type,
+ "task_id": task_id,
+ "session_id": session_id,
+ "timestamp": time.time(),
+ "data": data,
+ }
+ await self.broadcast(f"task:{task_id}", event)
+ await self.broadcast("logs", event)
+ await self.broadcast("agent_status", {
+ "type": "agent_event",
+ "task_id": task_id,
+ "event_type": event_type,
+ "timestamp": time.time(),
+ })
+
+ async def emit_chat(self, session_id: str, event_type: str, data: dict):
+ """Emit event to a chat session room."""
+ event = {
+ "type": event_type,
+ "session_id": session_id,
+ "timestamp": time.time(),
+ "data": data,
+ }
+ await self.broadcast(f"chat:{session_id}", event)
+
+ async def heartbeat_loop(self):
+ """Send heartbeat to all connections every 15s."""
+ while True:
+ await asyncio.sleep(15)
+ heartbeat = {
+ "type": "heartbeat",
+ "timestamp": time.time(),
+ "connections": self._connection_count,
+ }
+ for room in list(self._rooms.keys()):
+ await self.broadcast(room, heartbeat)
+
+ def get_stats(self) -> dict:
+ return {
+ "total_connections": self._connection_count,
+ "rooms": {r: len(ws) for r, ws in self._rooms.items()},
+ "buffered_events": {r: len(e) for r, e in self._event_buffer.items()},
+ }
diff --git a/backend/core/__init__.py b/backend/core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/core/__pycache__/__init__.cpython-312.pyc b/backend/core/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..379ed24d70cbb3676abd189e72df51c25c53be05
Binary files /dev/null and b/backend/core/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/core/__pycache__/agent.cpython-312.pyc b/backend/core/__pycache__/agent.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b5b1ebbbb408d883e9048adb2dd8992582281e2a
Binary files /dev/null and b/backend/core/__pycache__/agent.cpython-312.pyc differ
diff --git a/backend/core/__pycache__/models.cpython-312.pyc b/backend/core/__pycache__/models.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bb9d95933a1cb106e8eca4cd9eac10505e42bd2e
Binary files /dev/null and b/backend/core/__pycache__/models.cpython-312.pyc differ
diff --git a/backend/core/__pycache__/task_engine.cpython-312.pyc b/backend/core/__pycache__/task_engine.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..921c872cdb4610246211e471c629395245037bea
Binary files /dev/null and b/backend/core/__pycache__/task_engine.cpython-312.pyc differ
diff --git a/backend/core/agent.py b/backend/core/agent.py
new file mode 100644
index 0000000000000000000000000000000000000000..293d2d0b8356d476b1b4d00fafe7712457de8222
--- /dev/null
+++ b/backend/core/agent.py
@@ -0,0 +1,392 @@
+"""
+Agent Core β Planner + Executor + Self-Heal Loop
+LLM-powered with OpenAI/Anthropic support, streaming tokens
+"""
+
+import asyncio
+import json
+import os
+import time
+from typing import Any, Dict, List, Optional
+
+import httpx
+import structlog
+
+from core.models import TaskPlan, TaskStep
+from api.websocket_manager import WebSocketManager
+from memory.db import save_memory, get_history, search_memory
+
+log = structlog.get_logger()
+
+OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY", "")
+ANTHROPIC_API_KEY = os.environ.get("ANTHROPIC_API_KEY", "")
+DEFAULT_MODEL = os.environ.get("DEFAULT_MODEL", "gpt-4o")
+OPENAI_BASE_URL = os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1")
+
+
+SYSTEM_PROMPT = """You are an elite autonomous AI software engineer β like Devin or Manus.
+You can plan, code, debug, refactor, test, and deploy software autonomously.
+You think step-by-step, write production-quality code, and self-heal on errors.
+Always respond in structured JSON when asked for plans or structured output.
+"""
+
+PLANNER_PROMPT = """You are a senior software architect. Given a goal, produce a detailed execution plan.
+
+Respond ONLY with valid JSON:
+{
+ "steps": [
+ {
+ "name": "Step name",
+ "description": "What this step does",
+ "tool": "code|shell|file|browser|github|memory|search|test|none",
+ "estimated_seconds": 10
+ }
+ ],
+ "estimated_duration": 60,
+ "tools_needed": ["code", "shell"]
+}
+
+Goal: {goal}
+Context: {context}
+"""
+
+
+class AgentCore:
+ def __init__(self, ws_manager: WebSocketManager):
+ self.ws = ws_manager
+ self.model = DEFAULT_MODEL
+
+ # βββ LLM Call (with streaming) βββββββββββββββββββββββββββββββββββββββββββββ
+
+ async def llm_stream(
+ self,
+ messages: List[Dict],
+ task_id: str = "",
+ session_id: str = "",
+ model: str = "",
+ temperature: float = 0.7,
+ max_tokens: int = 4096,
+ ) -> str:
+ """Stream LLM tokens, emitting llm_chunk events via WebSocket."""
+ model = model or self.model
+ full_text = ""
+
+ if OPENAI_API_KEY:
+ full_text = await self._openai_stream(
+ messages, task_id, session_id, model, temperature, max_tokens
+ )
+ elif ANTHROPIC_API_KEY:
+ full_text = await self._anthropic_stream(
+ messages, task_id, session_id, temperature, max_tokens
+ )
+ else:
+ # Demo mode β simulate streaming
+ full_text = await self._demo_stream(messages, task_id, session_id)
+
+ return full_text
+
+ async def _openai_stream(
+ self, messages, task_id, session_id, model, temperature, max_tokens
+ ) -> str:
+ full_text = ""
+ headers = {
+ "Authorization": f"Bearer {OPENAI_API_KEY}",
+ "Content-Type": "application/json",
+ }
+ payload = {
+ "model": model,
+ "messages": messages,
+ "stream": True,
+ "temperature": temperature,
+ "max_tokens": max_tokens,
+ }
+ async with httpx.AsyncClient(timeout=120) as client:
+ async with client.stream(
+ "POST", f"{OPENAI_BASE_URL}/chat/completions",
+ headers=headers, json=payload
+ ) as resp:
+ resp.raise_for_status()
+ async for line in resp.aiter_lines():
+ if not line.startswith("data:"):
+ continue
+ chunk = line[6:].strip()
+ if chunk == "[DONE]":
+ break
+ try:
+ data = json.loads(chunk)
+ delta = data["choices"][0]["delta"].get("content", "")
+ if delta:
+ full_text += delta
+ if task_id:
+ await self.ws.emit(task_id, "llm_chunk", {
+ "chunk": delta,
+ "accumulated": len(full_text),
+ }, session_id=session_id)
+ if session_id and not task_id:
+ await self.ws.emit_chat(session_id, "llm_chunk", {
+ "chunk": delta,
+ })
+ except Exception:
+ pass
+ return full_text
+
+ async def _anthropic_stream(
+ self, messages, task_id, session_id, temperature, max_tokens
+ ) -> str:
+ full_text = ""
+ system = ""
+ filtered = []
+ for m in messages:
+ if m["role"] == "system":
+ system = m["content"]
+ else:
+ filtered.append(m)
+ headers = {
+ "x-api-key": ANTHROPIC_API_KEY,
+ "anthropic-version": "2023-06-01",
+ "Content-Type": "application/json",
+ }
+ payload = {
+ "model": "claude-3-5-sonnet-20241022",
+ "max_tokens": max_tokens,
+ "messages": filtered,
+ "stream": True,
+ }
+ if system:
+ payload["system"] = system
+ async with httpx.AsyncClient(timeout=120) as client:
+ async with client.stream(
+ "POST", "https://api.anthropic.com/v1/messages",
+ headers=headers, json=payload
+ ) as resp:
+ resp.raise_for_status()
+ async for line in resp.aiter_lines():
+ if not line.startswith("data:"):
+ continue
+ try:
+ data = json.loads(line[5:].strip())
+ if data.get("type") == "content_block_delta":
+ delta = data["delta"].get("text", "")
+ if delta:
+ full_text += delta
+ if task_id:
+ await self.ws.emit(task_id, "llm_chunk", {
+ "chunk": delta,
+ }, session_id=session_id)
+ if session_id and not task_id:
+ await self.ws.emit_chat(session_id, "llm_chunk", {
+ "chunk": delta,
+ })
+ except Exception:
+ pass
+ return full_text
+
+ async def _demo_stream(self, messages, task_id, session_id) -> str:
+ """Demo mode β simulate LLM streaming without API key."""
+ last_user = next(
+ (m["content"] for m in reversed(messages) if m["role"] == "user"), "Hello"
+ )
+ response = (
+ f"π€ **Devin Agent** (Demo Mode)\n\n"
+ f"I received your request: *{last_user[:100]}*\n\n"
+ f"To enable real AI responses, set `OPENAI_API_KEY` or `ANTHROPIC_API_KEY` in your environment.\n\n"
+ f"**What I can do with a real API key:**\n"
+ f"- π Generate detailed execution plans\n"
+ f"- π» Write and execute code autonomously\n"
+ f"- π§ Debug and self-heal on errors\n"
+ f"- π Manage GitHub repos autonomously\n"
+ f"- π§ Remember long-running project context\n"
+ f"- π Deploy applications automatically\n"
+ )
+ full_text = ""
+ for word in response.split():
+ chunk = word + " "
+ full_text += chunk
+ await asyncio.sleep(0.03)
+ if task_id:
+ await self.ws.emit(task_id, "llm_chunk", {
+ "chunk": chunk,
+ "demo": True,
+ }, session_id=session_id)
+ if session_id and not task_id:
+ await self.ws.emit_chat(session_id, "llm_chunk", {
+ "chunk": chunk,
+ "demo": True,
+ })
+ return full_text
+
+ # βββ Planning ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ async def plan(self, goal: str, task_id: str, session_id: str = "") -> TaskPlan:
+ """Generate a structured execution plan."""
+ # Get context from memory
+ memories = await search_memory(goal[:50], session_id=session_id)
+ context = "\n".join([m["content"][:200] for m in memories[:3]])
+
+ prompt = PLANNER_PROMPT.format(goal=goal, context=context or "No prior context")
+
+ messages = [
+ {"role": "system", "content": SYSTEM_PROMPT},
+ {"role": "user", "content": prompt},
+ ]
+
+ if not OPENAI_API_KEY and not ANTHROPIC_API_KEY:
+ # Demo plan
+ return self._demo_plan(goal)
+
+ raw = await self.llm_stream(messages, task_id=task_id, session_id=session_id)
+
+ # Extract JSON from response
+ try:
+ # Find JSON block
+ start = raw.find("{")
+ end = raw.rfind("}") + 1
+ if start >= 0 and end > start:
+ data = json.loads(raw[start:end])
+ else:
+ data = json.loads(raw)
+
+ steps = []
+ for i, s in enumerate(data.get("steps", [])):
+ steps.append(TaskStep(
+ name=s.get("name", f"Step {i+1}"),
+ description=s.get("description", ""),
+ tool=s.get("tool", "none"),
+ ))
+
+ return TaskPlan(
+ goal=goal,
+ steps=steps if steps else [TaskStep(name="Execute goal", description=goal, tool="code")],
+ estimated_duration=data.get("estimated_duration", 60),
+ tools_needed=data.get("tools_needed", []),
+ )
+ except Exception as e:
+ log.warning("Plan parse failed, using fallback", error=str(e))
+ return self._demo_plan(goal)
+
+ def _demo_plan(self, goal: str) -> TaskPlan:
+ """Fallback plan for demo mode."""
+ steps = [
+ TaskStep(name="Analyze Requirements", description=f"Analyze: {goal[:60]}", tool="none"),
+ TaskStep(name="Design Solution", description="Design the solution architecture", tool="none"),
+ TaskStep(name="Implement", description="Write the implementation code", tool="code"),
+ TaskStep(name="Test", description="Test the implementation", tool="test"),
+ TaskStep(name="Document", description="Write documentation", tool="none"),
+ ]
+ return TaskPlan(
+ goal=goal,
+ steps=steps,
+ estimated_duration=120,
+ tools_needed=["code", "test"],
+ )
+
+ # βββ Step Execution ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ async def execute_step(
+ self,
+ step: TaskStep,
+ task_id: str,
+ session_id: str = "",
+ context: Dict = {},
+ ) -> str:
+ """Execute a single step using the appropriate tool."""
+ from tools.executor import ToolExecutor
+ executor = ToolExecutor(self.ws)
+
+ await self.ws.emit(task_id, "tool_called", {
+ "tool": step.tool or "none",
+ "step": step.name,
+ "description": step.description,
+ }, session_id=session_id)
+
+ try:
+ result = await executor.run(
+ tool=step.tool or "none",
+ task=step.description,
+ goal=context.get("goal", ""),
+ previous=context.get("previous_results", []),
+ task_id=task_id,
+ session_id=session_id,
+ )
+ await self.ws.emit(task_id, "tool_result", {
+ "tool": step.tool,
+ "step": step.name,
+ "result": str(result)[:500],
+ "success": True,
+ }, session_id=session_id)
+ return result
+ except Exception as e:
+ await self.ws.emit(task_id, "tool_result", {
+ "tool": step.tool,
+ "step": step.name,
+ "error": str(e),
+ "success": False,
+ }, session_id=session_id)
+ return f"Error in {step.name}: {str(e)}"
+
+ # βββ Finalize ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ async def finalize(
+ self,
+ goal: str,
+ steps: List[TaskStep],
+ results: List[str],
+ task_id: str,
+ session_id: str = "",
+ ) -> str:
+ """Compile final result summary."""
+ steps_summary = "\n".join([
+ f"- {s.name}: {r[:200]}" for s, r in zip(steps, results)
+ ])
+ messages = [
+ {"role": "system", "content": SYSTEM_PROMPT},
+ {"role": "user", "content": (
+ f"Summarize the completion of this goal:\n"
+ f"Goal: {goal}\n\n"
+ f"Steps completed:\n{steps_summary}\n\n"
+ f"Write a concise success summary with key outcomes."
+ )},
+ ]
+ result = await self.llm_stream(messages, task_id=task_id, session_id=session_id)
+ return result or f"β
Completed: {goal}"
+
+ # βββ Chat ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ async def stream_chat(self, session_id: str, user_message: str):
+ """Stream a conversational chat response."""
+ # Save user message to memory
+ await save_memory(
+ content=user_message,
+ memory_type="conversation",
+ session_id=session_id,
+ key="user_message",
+ )
+
+ # Get conversation history
+ history = await get_history(session_id, limit=10)
+ messages = [{"role": "system", "content": SYSTEM_PROMPT}]
+ for h in reversed(history[-10:]):
+ messages.append({"role": "user", "content": h["content"]})
+
+ messages.append({"role": "user", "content": user_message})
+
+ await self.ws.emit_chat(session_id, "stream_start", {
+ "status": "generating",
+ })
+
+ response = await self.llm_stream(messages, session_id=session_id)
+
+ # Save assistant response to memory
+ await save_memory(
+ content=response,
+ memory_type="conversation",
+ session_id=session_id,
+ key="assistant_response",
+ )
+
+ await self.ws.emit_chat(session_id, "stream_end", {
+ "full_response": response,
+ "status": "complete",
+ })
+
+ return response
diff --git a/backend/core/models.py b/backend/core/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..772070923d8ddf8b5cba203e2d64ddb31c010fe6
--- /dev/null
+++ b/backend/core/models.py
@@ -0,0 +1,213 @@
+"""
+Pydantic Models β Task, Chat, Memory, GitHub
+"""
+
+import time
+import uuid
+from enum import Enum
+from typing import Any, Dict, List, Optional
+from pydantic import BaseModel, Field
+
+
+def gen_id(prefix: str = "") -> str:
+ return f"{prefix}{uuid.uuid4().hex[:12]}"
+
+
+# βββ Enums βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+class TaskStatus(str, Enum):
+ queued = "queued"
+ initializing = "initializing"
+ planning = "planning"
+ executing = "executing"
+ streaming = "streaming"
+ waiting_input = "waiting_input"
+ retrying = "retrying"
+ finalizing = "finalizing"
+ completed = "completed"
+ failed = "failed"
+ cancelled = "cancelled"
+
+
+class EventType(str, Enum):
+ task_created = "task_created"
+ task_queued = "task_queued"
+ task_started = "task_started"
+ plan_generated = "plan_generated"
+ step_started = "step_started"
+ step_progress = "step_progress"
+ tool_called = "tool_called"
+ tool_result = "tool_result"
+ llm_chunk = "llm_chunk"
+ memory_updated = "memory_updated"
+ retry_attempt = "retry_attempt"
+ step_completed = "step_completed"
+ warning = "warning"
+ error = "error"
+ task_completed = "task_completed"
+ task_failed = "task_failed"
+ heartbeat = "heartbeat"
+
+
+class MemoryType(str, Enum):
+ conversation = "conversation"
+ task = "task"
+ project = "project"
+ execution = "execution"
+ tool = "tool"
+ error = "error"
+ repo = "repo"
+ planning = "planning"
+
+
+# βββ Task Models βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+class TaskCreateRequest(BaseModel):
+ goal: str = Field(..., min_length=1, max_length=10000, description="What should the agent do?")
+ session_id: str = Field(default_factory=lambda: gen_id("sess_"))
+ project_id: str = Field(default="")
+ stream: bool = True
+ metadata: Dict[str, Any] = Field(default_factory=dict)
+ github_repo: Optional[str] = None
+ auto_commit: bool = False
+
+
+class TaskStep(BaseModel):
+ id: str = Field(default_factory=lambda: gen_id("step_"))
+ name: str
+ description: str = ""
+ tool: Optional[str] = None
+ status: str = "pending"
+ output: Optional[str] = None
+ error: Optional[str] = None
+ started_at: Optional[float] = None
+ completed_at: Optional[float] = None
+ duration_ms: Optional[float] = None
+
+
+class TaskPlan(BaseModel):
+ goal: str
+ steps: List[TaskStep]
+ estimated_duration: int = 0
+ tools_needed: List[str] = []
+ created_at: float = Field(default_factory=time.time)
+
+
+class TaskResponse(BaseModel):
+ id: str
+ goal: str
+ status: TaskStatus
+ session_id: str
+ project_id: str
+ plan: Optional[TaskPlan] = None
+ result: Optional[str] = None
+ error: Optional[str] = None
+ created_at: float
+ started_at: Optional[float] = None
+ completed_at: Optional[float] = None
+ retry_count: int = 0
+ stream_url: Optional[str] = None
+ ws_url: Optional[str] = None
+
+
+class TaskCancelRequest(BaseModel):
+ reason: str = "User cancelled"
+
+
+class TaskRetryRequest(BaseModel):
+ reset_state: bool = True
+
+
+# βββ Chat Models βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+class ChatMessage(BaseModel):
+ role: str = Field(..., pattern="^(user|assistant|system)$")
+ content: str
+ timestamp: float = Field(default_factory=time.time)
+
+
+class ChatRequest(BaseModel):
+ messages: List[ChatMessage]
+ session_id: str = Field(default_factory=lambda: gen_id("sess_"))
+ project_id: str = ""
+ stream: bool = True
+ model: str = "gpt-4o"
+ temperature: float = 0.7
+ max_tokens: int = 4096
+ system_prompt: Optional[str] = None
+
+
+class GoalRequest(BaseModel):
+ goal: str = Field(..., min_length=1, max_length=10000)
+ session_id: str = Field(default_factory=lambda: gen_id("sess_"))
+ project_id: str = ""
+ stream: bool = True
+ auto_execute: bool = True
+ github_repo: Optional[str] = None
+
+
+# βββ Memory Models βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+class MemorySaveRequest(BaseModel):
+ content: str
+ memory_type: MemoryType
+ session_id: str = ""
+ project_id: str = ""
+ key: str = ""
+ metadata: Dict[str, Any] = {}
+
+
+class MemorySearchRequest(BaseModel):
+ query: str
+ session_id: str = ""
+ project_id: str = ""
+ limit: int = 20
+
+
+# βββ GitHub Models βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+class GitHubCloneRequest(BaseModel):
+ repo_url: str
+ branch: str = "main"
+ local_path: Optional[str] = None
+
+
+class GitHubCreateRepoRequest(BaseModel):
+ name: str
+ description: str = ""
+ private: bool = False
+ auto_init: bool = True
+
+
+class GitHubCommitRequest(BaseModel):
+ repo: str
+ branch: str = "main"
+ files: Dict[str, str] # path β content
+ message: str
+ create_branch: bool = False
+
+
+class GitHubPRRequest(BaseModel):
+ repo: str
+ title: str
+ body: str = ""
+ head: str
+ base: str = "main"
+ draft: bool = False
+
+
+class GitHubIssueRequest(BaseModel):
+ repo: str
+ title: str
+ body: str = ""
+ labels: List[str] = []
+
+
+# βββ Event Schema (unified) ββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+class StreamEvent(BaseModel):
+ type: str
+ task_id: str = ""
+ session_id: str = ""
+ timestamp: float = Field(default_factory=time.time)
+ data: Dict[str, Any] = {}
diff --git a/backend/core/task_engine.py b/backend/core/task_engine.py
new file mode 100644
index 0000000000000000000000000000000000000000..6eeb28d10be4e264994dc2b16beafb7d8b2492b6
--- /dev/null
+++ b/backend/core/task_engine.py
@@ -0,0 +1,241 @@
+"""
+Task Engine β Heart of the Autonomous Agent
+Manages task lifecycle, planning, execution, self-healing
+"""
+
+import asyncio
+import json
+import os
+import time
+import uuid
+from typing import Dict, Optional, List
+
+import structlog
+
+from core.models import TaskStatus, TaskPlan, TaskStep, TaskCreateRequest
+from api.websocket_manager import WebSocketManager
+from memory.db import (
+ create_task, update_task_status, get_task, save_task_event,
+ save_memory, get_task_events
+)
+
+log = structlog.get_logger()
+
+MAX_RETRIES = 3
+MAX_CONCURRENT = 5
+
+
+class TaskEngine:
+ def __init__(self, ws_manager: WebSocketManager):
+ self.ws = ws_manager
+ self._queue: asyncio.Queue = asyncio.Queue()
+ self._active: Dict[str, asyncio.Task] = {}
+ self._running = False
+ self._workers: List[asyncio.Task] = []
+
+ async def start(self):
+ self._running = True
+ for i in range(MAX_CONCURRENT):
+ worker = asyncio.create_task(self._worker(i))
+ self._workers.append(worker)
+ log.info("TaskEngine started", workers=MAX_CONCURRENT)
+
+ async def stop(self):
+ self._running = False
+ for w in self._workers:
+ w.cancel()
+ log.info("TaskEngine stopped")
+
+ # βββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ async def submit(self, req: TaskCreateRequest) -> str:
+ task_id = f"task_{uuid.uuid4().hex[:10]}"
+ await create_task(
+ task_id=task_id,
+ goal=req.goal,
+ session_id=req.session_id,
+ project_id=req.project_id,
+ metadata={**req.metadata, "github_repo": req.github_repo, "auto_commit": req.auto_commit},
+ )
+ await self.ws.emit(task_id, "task_created", {
+ "goal": req.goal,
+ "session_id": req.session_id,
+ "stream_url": f"/api/v1/tasks/{task_id}/stream",
+ "ws_url": f"/ws/tasks/{task_id}",
+ }, session_id=req.session_id)
+ await self._queue.put((task_id, req))
+ await self.ws.emit(task_id, "task_queued", {
+ "position": self._queue.qsize(),
+ }, session_id=req.session_id)
+ log.info("Task submitted", task_id=task_id, goal=req.goal[:60])
+ return task_id
+
+ async def cancel(self, task_id: str, reason: str = "User cancelled"):
+ if task_id in self._active:
+ self._active[task_id].cancel()
+ del self._active[task_id]
+ await update_task_status(task_id, "cancelled", error=reason)
+ await self.ws.emit(task_id, "task_failed", {"reason": reason, "status": "cancelled"})
+
+ async def retry(self, task_id: str):
+ task = await get_task(task_id)
+ if not task:
+ return
+ req = TaskCreateRequest(
+ goal=task["goal"],
+ session_id=task["session_id"] or "",
+ project_id=task["project_id"] or "",
+ metadata=task.get("metadata") or {},
+ )
+ retry_count = (task.get("retry_count") or 0) + 1
+ await update_task_status(task_id, "queued", retry_count=retry_count)
+ await self.ws.emit(task_id, "retry_attempt", {"count": retry_count})
+ await self._queue.put((task_id, req))
+
+ async def handle_chat_message(self, session_id: str, content: str, websocket=None):
+ """Handle real-time chat message with streaming response."""
+ from core.agent import AgentCore
+ agent = AgentCore(self.ws)
+ await agent.stream_chat(session_id=session_id, user_message=content)
+
+ # βββ Worker Loop βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+ async def _worker(self, worker_id: int):
+ log.info(f"Worker {worker_id} started")
+ while self._running:
+ try:
+ task_id, req = await asyncio.wait_for(self._queue.get(), timeout=1.0)
+ worker_task = asyncio.create_task(self._execute(task_id, req))
+ self._active[task_id] = worker_task
+ await worker_task
+ self._active.pop(task_id, None)
+ self._queue.task_done()
+ except asyncio.TimeoutError:
+ continue
+ except asyncio.CancelledError:
+ break
+ except Exception as e:
+ log.error(f"Worker {worker_id} error", error=str(e))
+
+ async def _execute(self, task_id: str, req: TaskCreateRequest):
+ """Full task execution lifecycle."""
+ from core.agent import AgentCore
+ agent = AgentCore(self.ws)
+
+ try:
+ # ββ Initializing ββββββββββββββββββββββββββββββββββββββββββββββββ
+ await update_task_status(task_id, "initializing")
+ await self.ws.emit(task_id, "task_started", {
+ "goal": req.goal,
+ "status": "initializing",
+ }, session_id=req.session_id)
+ await save_task_event(task_id, "task_started", {"goal": req.goal})
+
+ # ββ Planning ββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ await update_task_status(task_id, "planning")
+ await self.ws.emit(task_id, "step_started", {
+ "step": "Planning",
+ "status": "planning",
+ "description": "Generating execution plan...",
+ }, session_id=req.session_id)
+
+ plan = await agent.plan(goal=req.goal, task_id=task_id, session_id=req.session_id)
+
+ await update_task_status(task_id, "executing", plan=plan.model_dump())
+ await self.ws.emit(task_id, "plan_generated", {
+ "steps": [s.model_dump() for s in plan.steps],
+ "estimated_duration": plan.estimated_duration,
+ "tools_needed": plan.tools_needed,
+ }, session_id=req.session_id)
+ await save_task_event(task_id, "plan_generated", {"steps_count": len(plan.steps)})
+
+ # ββ Execute Steps ββββββββββββββββββββββββββββββββββββββββββββββββ
+ results = []
+ for i, step in enumerate(plan.steps):
+ await self.ws.emit(task_id, "step_started", {
+ "step": step.name,
+ "step_id": step.id,
+ "index": i,
+ "total": len(plan.steps),
+ "tool": step.tool,
+ }, session_id=req.session_id)
+
+ step_result = await agent.execute_step(
+ step=step,
+ task_id=task_id,
+ session_id=req.session_id,
+ context={"goal": req.goal, "previous_results": results},
+ )
+ results.append(step_result)
+
+ await self.ws.emit(task_id, "step_completed", {
+ "step": step.name,
+ "step_id": step.id,
+ "index": i,
+ "output": step_result[:500] if isinstance(step_result, str) else str(step_result)[:500],
+ "status": "completed",
+ }, session_id=req.session_id)
+ await save_task_event(task_id, "step_completed", {"step": step.name, "index": i})
+
+ # ββ Finalize βββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ await update_task_status(task_id, "finalizing")
+ await self.ws.emit(task_id, "step_started", {
+ "step": "Finalizing",
+ "description": "Compiling results...",
+ }, session_id=req.session_id)
+
+ final_result = await agent.finalize(
+ goal=req.goal,
+ steps=plan.steps,
+ results=results,
+ task_id=task_id,
+ session_id=req.session_id,
+ )
+
+ await update_task_status(task_id, "completed", result=final_result)
+ await self.ws.emit(task_id, "task_completed", {
+ "result": final_result,
+ "steps_completed": len(plan.steps),
+ "duration": time.time(),
+ }, session_id=req.session_id)
+
+ # Save to memory
+ await save_memory(
+ content=f"Task: {req.goal}\nResult: {final_result}",
+ memory_type="task",
+ session_id=req.session_id,
+ project_id=req.project_id,
+ key=task_id,
+ )
+ await self.ws.emit(task_id, "memory_updated", {
+ "type": "task",
+ "key": task_id,
+ }, session_id=req.session_id)
+
+ log.info("Task completed", task_id=task_id)
+
+ except asyncio.CancelledError:
+ await update_task_status(task_id, "cancelled")
+ await self.ws.emit(task_id, "task_failed", {"reason": "cancelled"})
+ except Exception as e:
+ log.error("Task failed", task_id=task_id, error=str(e))
+ task_data = await get_task(task_id)
+ retry_count = (task_data or {}).get("retry_count", 0)
+
+ await self.ws.emit(task_id, "error", {
+ "error": str(e),
+ "retry_count": retry_count,
+ "will_retry": retry_count < MAX_RETRIES,
+ }, session_id=req.session_id)
+
+ if retry_count < MAX_RETRIES:
+ await update_task_status(task_id, "retrying", retry_count=retry_count + 1)
+ await asyncio.sleep(2 ** retry_count)
+ await self.ws.emit(task_id, "retry_attempt", {"count": retry_count + 1})
+ await self._execute(task_id, req)
+ else:
+ await update_task_status(task_id, "failed", error=str(e))
+ await self.ws.emit(task_id, "task_failed", {
+ "error": str(e),
+ "retry_count": retry_count,
+ }, session_id=req.session_id)
diff --git a/backend/ecosystem.config.cjs b/backend/ecosystem.config.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..ddba4e6b870059011d68d1ed7ec67d06c1a86771
--- /dev/null
+++ b/backend/ecosystem.config.cjs
@@ -0,0 +1,20 @@
+module.exports = {
+ apps: [
+ {
+ name: 'devin-backend',
+ script: 'uvicorn',
+ args: 'main:app --host 0.0.0.0 --port 7860 --loop asyncio --log-level info',
+ interpreter: 'python3',
+ cwd: '/home/user/devin-agent/backend',
+ watch: false,
+ instances: 1,
+ exec_mode: 'fork',
+ env: {
+ PORT: 7860,
+ HOST: '0.0.0.0',
+ DB_PATH: '/tmp/devin_agent.db',
+ PYTHONUNBUFFERED: '1',
+ },
+ },
+ ],
+}
diff --git a/backend/github/__init__.py b/backend/github/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/main.py b/backend/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..6202e92b7b308de38693263dcb15fb815df68418
--- /dev/null
+++ b/backend/main.py
@@ -0,0 +1,180 @@
+"""
+π Devin-Style Autonomous AI Engineering Platform
+Production-Grade FastAPI + WebSocket Backend
+"""
+
+import asyncio
+import json
+import logging
+import os
+import time
+import uuid
+from contextlib import asynccontextmanager
+from typing import Optional
+
+import structlog
+from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Depends, Request
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.middleware.gzip import GZipMiddleware
+from fastapi.responses import JSONResponse
+from slowapi import Limiter, _rate_limit_exceeded_handler
+from slowapi.util import get_remote_address
+from slowapi.errors import RateLimitExceeded
+
+from api.routes import tasks, chat, memory, github, health
+from api.websocket_manager import WebSocketManager
+from core.task_engine import TaskEngine
+from memory.db import init_db
+
+# βββ Structured Logging ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+structlog.configure(
+ processors=[
+ structlog.processors.TimeStamper(fmt="iso"),
+ structlog.stdlib.add_log_level,
+ structlog.processors.StackInfoRenderer(),
+ structlog.dev.ConsoleRenderer(),
+ ]
+)
+log = structlog.get_logger()
+
+# βββ Rate Limiter ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+limiter = Limiter(key_func=get_remote_address)
+
+# βββ Global Managers (shared state) βββββββββββββββββββββββββββββββββββββββββββ
+ws_manager = WebSocketManager()
+task_engine = TaskEngine(ws_manager)
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ """Startup + Shutdown lifecycle."""
+ log.info("π Starting Devin Agent Platform...")
+ await init_db()
+ await task_engine.start()
+ asyncio.create_task(ws_manager.heartbeat_loop())
+ log.info("β
Platform ready")
+ yield
+ log.info("π Shutting down...")
+ await task_engine.stop()
+ log.info("β
Shutdown complete")
+
+
+# βββ FastAPI App βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+app = FastAPI(
+ title="π€ Devin Agent Platform",
+ description="Production-Grade Autonomous AI Engineering Platform",
+ version="2.0.0",
+ lifespan=lifespan,
+ docs_url="/api/docs",
+ redoc_url="/api/redoc",
+)
+
+app.state.limiter = limiter
+app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
+
+# βββ Middleware ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+app.add_middleware(GZipMiddleware, minimum_size=1000)
+
+
+# βββ Request Logging βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+@app.middleware("http")
+async def log_requests(request: Request, call_next):
+ start = time.time()
+ response = await call_next(request)
+ duration = round((time.time() - start) * 1000, 2)
+ log.info("HTTP", method=request.method, path=request.url.path, status=response.status_code, ms=duration)
+ return response
+
+
+# βββ Inject shared state into routes ββββββββββββββββββββββββββββββββββββββββββ
+app.state.ws_manager = ws_manager
+app.state.task_engine = task_engine
+
+
+# βββ REST API Routers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+app.include_router(health.router, prefix="/api/v1", tags=["health"])
+app.include_router(tasks.router, prefix="/api/v1/tasks", tags=["tasks"])
+app.include_router(chat.router, prefix="/api/v1", tags=["chat"])
+app.include_router(memory.router, prefix="/api/v1/memory", tags=["memory"])
+app.include_router(github.router, prefix="/api/v1/github", tags=["github"])
+
+
+# βββ WebSocket Endpoints βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+@app.websocket("/ws/tasks/{task_id}")
+async def ws_task(websocket: WebSocket, task_id: str):
+ """Live streaming for specific task execution."""
+ await ws_manager.connect(websocket, room=f"task:{task_id}")
+ try:
+ while True:
+ data = await websocket.receive_text()
+ msg = json.loads(data)
+ if msg.get("type") == "ping":
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
+ except WebSocketDisconnect:
+ ws_manager.disconnect(websocket, room=f"task:{task_id}")
+
+
+@app.websocket("/ws/logs")
+async def ws_logs(websocket: WebSocket):
+ """Global live log stream."""
+ await ws_manager.connect(websocket, room="logs")
+ try:
+ while True:
+ data = await websocket.receive_text()
+ msg = json.loads(data)
+ if msg.get("type") == "ping":
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
+ except WebSocketDisconnect:
+ ws_manager.disconnect(websocket, room="logs")
+
+
+@app.websocket("/ws/chat/{session_id}")
+async def ws_chat(websocket: WebSocket, session_id: str):
+ """Real-time chat streaming per session."""
+ await ws_manager.connect(websocket, room=f"chat:{session_id}")
+ try:
+ while True:
+ data = await websocket.receive_text()
+ msg = json.loads(data)
+ if msg.get("type") == "ping":
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
+ elif msg.get("type") == "chat_message":
+ # Trigger streaming chat response
+ asyncio.create_task(
+ task_engine.handle_chat_message(session_id, msg.get("content", ""), websocket)
+ )
+ except WebSocketDisconnect:
+ ws_manager.disconnect(websocket, room=f"chat:{session_id}")
+
+
+@app.websocket("/ws/agent/status")
+async def ws_agent_status(websocket: WebSocket):
+ """Global agent status stream."""
+ await ws_manager.connect(websocket, room="agent_status")
+ try:
+ while True:
+ data = await websocket.receive_text()
+ msg = json.loads(data)
+ if msg.get("type") == "ping":
+ await websocket.send_json({"type": "pong", "timestamp": time.time()})
+ except WebSocketDisconnect:
+ ws_manager.disconnect(websocket, room="agent_status")
+
+
+# βββ Root ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+@app.get("/")
+async def root():
+ return {
+ "name": "π€ Devin Agent Platform",
+ "version": "2.0.0",
+ "status": "operational",
+ "docs": "/api/docs",
+ "websockets": ["/ws/tasks/{task_id}", "/ws/logs", "/ws/chat/{session_id}", "/ws/agent/status"],
+ }
diff --git a/backend/memory/__init__.py b/backend/memory/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/memory/__pycache__/__init__.cpython-312.pyc b/backend/memory/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1952a0ff6a2a22048f4412f42cc0f824a14104de
Binary files /dev/null and b/backend/memory/__pycache__/__init__.cpython-312.pyc differ
diff --git a/backend/memory/__pycache__/db.cpython-312.pyc b/backend/memory/__pycache__/db.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5592e627062b9e4e02c1ac97ff7f9b8f152e8b15
Binary files /dev/null and b/backend/memory/__pycache__/db.cpython-312.pyc differ
diff --git a/backend/memory/db.py b/backend/memory/db.py
new file mode 100644
index 0000000000000000000000000000000000000000..68ae8bfba79386be88580dd8e5efcc89714c97d8
--- /dev/null
+++ b/backend/memory/db.py
@@ -0,0 +1,271 @@
+"""
+Production SQLite Database β Async via aiosqlite
+Handles tasks, memory, sessions, events
+"""
+
+import aiosqlite
+import os
+import json
+import time
+from typing import Optional, List, Dict, Any
+import structlog
+
+log = structlog.get_logger()
+
+DB_PATH = os.environ.get("DB_PATH", "/tmp/devin_agent.db")
+
+
+async def get_db() -> aiosqlite.Connection:
+ db = await aiosqlite.connect(DB_PATH)
+ db.row_factory = aiosqlite.Row
+ await db.execute("PRAGMA journal_mode=WAL")
+ await db.execute("PRAGMA foreign_keys=ON")
+ return db
+
+
+async def init_db():
+ """Initialize all tables."""
+ log.info("Initializing database", path=DB_PATH)
+ async with aiosqlite.connect(DB_PATH) as db:
+ await db.execute("PRAGMA journal_mode=WAL")
+ await db.execute("PRAGMA foreign_keys=ON")
+
+ # Tasks table
+ await db.execute("""
+ CREATE TABLE IF NOT EXISTS tasks (
+ id TEXT PRIMARY KEY,
+ session_id TEXT,
+ project_id TEXT,
+ goal TEXT NOT NULL,
+ status TEXT DEFAULT 'queued',
+ plan TEXT,
+ result TEXT,
+ error TEXT,
+ metadata TEXT DEFAULT '{}',
+ created_at REAL,
+ started_at REAL,
+ completed_at REAL,
+ retry_count INTEGER DEFAULT 0
+ )
+ """)
+
+ # Task events table
+ await db.execute("""
+ CREATE TABLE IF NOT EXISTS task_events (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ task_id TEXT NOT NULL,
+ event_type TEXT NOT NULL,
+ data TEXT DEFAULT '{}',
+ timestamp REAL,
+ FOREIGN KEY (task_id) REFERENCES tasks(id)
+ )
+ """)
+
+ # Memory table
+ await db.execute("""
+ CREATE TABLE IF NOT EXISTS memory (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ session_id TEXT,
+ project_id TEXT,
+ memory_type TEXT NOT NULL,
+ key TEXT,
+ content TEXT NOT NULL,
+ metadata TEXT DEFAULT '{}',
+ embedding TEXT,
+ created_at REAL,
+ updated_at REAL
+ )
+ """)
+
+ # Sessions table
+ await db.execute("""
+ CREATE TABLE IF NOT EXISTS sessions (
+ id TEXT PRIMARY KEY,
+ project_id TEXT,
+ user_id TEXT,
+ metadata TEXT DEFAULT '{}',
+ created_at REAL,
+ last_active REAL
+ )
+ """)
+
+ # GitHub operations table
+ await db.execute("""
+ CREATE TABLE IF NOT EXISTS github_ops (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ task_id TEXT,
+ operation TEXT NOT NULL,
+ repo TEXT,
+ branch TEXT,
+ status TEXT DEFAULT 'pending',
+ result TEXT,
+ created_at REAL
+ )
+ """)
+
+ # Indexes
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id)")
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)")
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_events_task ON task_events(task_id)")
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_memory_session ON memory(session_id)")
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_memory_project ON memory(project_id)")
+ await db.execute("CREATE INDEX IF NOT EXISTS idx_memory_type ON memory(memory_type)")
+
+ await db.commit()
+ log.info("β
Database initialized")
+
+
+# βββ Task CRUD βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+async def create_task(task_id: str, goal: str, session_id: str = "", project_id: str = "", metadata: dict = {}):
+ async with aiosqlite.connect(DB_PATH) as db:
+ await db.execute("""
+ INSERT INTO tasks (id, session_id, project_id, goal, status, metadata, created_at)
+ VALUES (?, ?, ?, ?, 'queued', ?, ?)
+ """, (task_id, session_id, project_id, goal, json.dumps(metadata), time.time()))
+ await db.commit()
+
+
+async def update_task_status(task_id: str, status: str, **kwargs):
+ fields = ["status = ?"]
+ values = [status]
+ if status == "executing":
+ fields.append("started_at = ?")
+ values.append(time.time())
+ if status in ("completed", "failed", "cancelled"):
+ fields.append("completed_at = ?")
+ values.append(time.time())
+ for k, v in kwargs.items():
+ if k in ("plan", "result", "error"):
+ fields.append(f"{k} = ?")
+ values.append(v if isinstance(v, str) else json.dumps(v))
+ elif k == "retry_count":
+ fields.append("retry_count = ?")
+ values.append(v)
+ values.append(task_id)
+ async with aiosqlite.connect(DB_PATH) as db:
+ await db.execute(f"UPDATE tasks SET {', '.join(fields)} WHERE id = ?", values)
+ await db.commit()
+
+
+async def get_task(task_id: str) -> Optional[Dict]:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ async with db.execute("SELECT * FROM tasks WHERE id = ?", (task_id,)) as cursor:
+ row = await cursor.fetchone()
+ if row:
+ d = dict(row)
+ d["metadata"] = json.loads(d.get("metadata") or "{}")
+ d["plan"] = json.loads(d["plan"]) if d.get("plan") else None
+ return d
+ return None
+
+
+async def list_tasks(session_id: str = "", limit: int = 50) -> List[Dict]:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ if session_id:
+ async with db.execute(
+ "SELECT * FROM tasks WHERE session_id = ? ORDER BY created_at DESC LIMIT ?",
+ (session_id, limit)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ else:
+ async with db.execute(
+ "SELECT * FROM tasks ORDER BY created_at DESC LIMIT ?", (limit,)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
+
+
+async def save_task_event(task_id: str, event_type: str, data: dict = {}):
+ async with aiosqlite.connect(DB_PATH) as db:
+ await db.execute("""
+ INSERT INTO task_events (task_id, event_type, data, timestamp)
+ VALUES (?, ?, ?, ?)
+ """, (task_id, event_type, json.dumps(data), time.time()))
+ await db.commit()
+
+
+async def get_task_events(task_id: str) -> List[Dict]:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ async with db.execute(
+ "SELECT * FROM task_events WHERE task_id = ? ORDER BY timestamp ASC", (task_id,)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
+
+
+# βββ Memory CRUD βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+async def save_memory(
+ content: str,
+ memory_type: str,
+ session_id: str = "",
+ project_id: str = "",
+ key: str = "",
+ metadata: dict = {}
+):
+ now = time.time()
+ async with aiosqlite.connect(DB_PATH) as db:
+ await db.execute("""
+ INSERT INTO memory (session_id, project_id, memory_type, key, content, metadata, created_at, updated_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
+ """, (session_id, project_id, memory_type, key, content, json.dumps(metadata), now, now))
+ await db.commit()
+
+
+async def search_memory(query: str, session_id: str = "", project_id: str = "", limit: int = 20) -> List[Dict]:
+ """Simple keyword search (upgrade to vector search in production)."""
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ q = f"%{query}%"
+ if session_id:
+ async with db.execute(
+ "SELECT * FROM memory WHERE session_id = ? AND content LIKE ? ORDER BY updated_at DESC LIMIT ?",
+ (session_id, q, limit)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ elif project_id:
+ async with db.execute(
+ "SELECT * FROM memory WHERE project_id = ? AND content LIKE ? ORDER BY updated_at DESC LIMIT ?",
+ (project_id, q, limit)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ else:
+ async with db.execute(
+ "SELECT * FROM memory WHERE content LIKE ? ORDER BY updated_at DESC LIMIT ?",
+ (q, limit)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
+
+
+async def get_project_memory(project_id: str, memory_type: str = "", limit: int = 100) -> List[Dict]:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ if memory_type:
+ async with db.execute(
+ "SELECT * FROM memory WHERE project_id = ? AND memory_type = ? ORDER BY updated_at DESC LIMIT ?",
+ (project_id, memory_type, limit)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ else:
+ async with db.execute(
+ "SELECT * FROM memory WHERE project_id = ? ORDER BY updated_at DESC LIMIT ?",
+ (project_id, limit)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
+
+
+async def get_history(session_id: str, limit: int = 50) -> List[Dict]:
+ async with aiosqlite.connect(DB_PATH) as db:
+ db.row_factory = aiosqlite.Row
+ async with db.execute(
+ "SELECT * FROM memory WHERE session_id = ? AND memory_type = 'conversation' ORDER BY created_at DESC LIMIT ?",
+ (session_id, limit)
+ ) as cursor:
+ rows = await cursor.fetchall()
+ return [dict(r) for r in rows]
diff --git a/backend/requirements.txt b/backend/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..c2dc8344489a23accea6972db25fe35a2feacfa8
--- /dev/null
+++ b/backend/requirements.txt
@@ -0,0 +1,28 @@
+fastapi==0.111.0
+uvicorn[standard]==0.29.0
+websockets==12.0
+pydantic==2.7.1
+pydantic-settings==2.2.1
+python-jose[cryptography]==3.3.0
+python-multipart==0.0.9
+aiohttp==3.9.5
+aiosqlite==0.20.0
+sqlalchemy[asyncio]==2.0.30
+alembic==1.13.1
+httpx==0.27.0
+openai==1.30.1
+anthropic==0.26.1
+gitpython==3.1.43
+pygithub==2.3.0
+python-dotenv==1.0.1
+slowapi==0.1.9
+structlog==24.1.0
+rich==13.7.1
+asyncio-mqtt==0.16.2
+redis==5.0.4
+celery==5.3.6
+passlib[bcrypt]==1.7.4
+cryptography==42.0.7
+typer==0.12.3
+watchfiles==0.21.0
+psutil==5.9.8
diff --git a/backend/tools/__init__.py b/backend/tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/backend/tools/executor.py b/backend/tools/executor.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ae0b80afbd92030c28334a35653c342c2599eb4
--- /dev/null
+++ b/backend/tools/executor.py
@@ -0,0 +1,176 @@
+"""
+Tool Executor β Routes tool calls to the right implementation
+Supports: code, shell, file, browser, github, memory, search, test, none
+"""
+
+import asyncio
+import os
+import subprocess
+import tempfile
+import time
+from typing import Any, List, Optional
+
+import structlog
+
+from api.websocket_manager import WebSocketManager
+
+log = structlog.get_logger()
+
+
+class ToolExecutor:
+ def __init__(self, ws_manager: WebSocketManager):
+ self.ws = ws_manager
+
+ async def run(
+ self,
+ tool: str,
+ task: str,
+ goal: str = "",
+ previous: List = [],
+ task_id: str = "",
+ session_id: str = "",
+ ) -> str:
+ tool = (tool or "none").lower().strip()
+
+ dispatch = {
+ "code": self._tool_code,
+ "shell": self._tool_shell,
+ "file": self._tool_file,
+ "github": self._tool_github,
+ "memory": self._tool_memory,
+ "search": self._tool_search,
+ "test": self._tool_test,
+ "browser": self._tool_browser,
+ "none": self._tool_none,
+ }
+
+ fn = dispatch.get(tool, self._tool_none)
+ return await fn(task=task, goal=goal, previous=previous, task_id=task_id, session_id=session_id)
+
+ # βββ Code Tool βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_code(self, task, goal, previous, task_id, session_id) -> str:
+ """Generate code using LLM."""
+ from core.agent import AgentCore
+ agent = AgentCore(self.ws)
+ messages = [
+ {"role": "system", "content": "You are an expert software engineer. Write clean, production-quality code. Return only the code with minimal explanation."},
+ {"role": "user", "content": f"Task: {task}\nGoal: {goal}\n\nWrite the code to accomplish this."},
+ ]
+ result = await agent.llm_stream(messages, task_id=task_id, session_id=session_id)
+ return result or f"# Code for: {task}"
+
+ # βββ Shell Tool ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_shell(self, task, goal, previous, task_id, session_id) -> str:
+ """Execute shell commands safely in a temp workspace."""
+ # Extract command from task description
+ from core.agent import AgentCore
+ agent = AgentCore(self.ws)
+ messages = [
+ {"role": "system", "content": "Extract the shell command to run. Return ONLY the command, nothing else."},
+ {"role": "user", "content": f"Task: {task}"},
+ ]
+ cmd = await agent.llm_stream(messages, task_id=task_id, session_id=session_id)
+ cmd = cmd.strip().strip("`").strip()
+
+ # Safety: block dangerous commands
+ blocked = ["rm -rf /", ":(){ :|:& };:", "mkfs", "dd if=", "shutdown", "reboot", "halt"]
+ for b in blocked:
+ if b in cmd:
+ return f"β Blocked dangerous command: {cmd}"
+
+ try:
+ await self.ws.emit(task_id, "step_progress", {
+ "action": "shell_exec",
+ "command": cmd[:200],
+ }, session_id=session_id)
+ proc = await asyncio.create_subprocess_shell(
+ cmd,
+ stdout=asyncio.subprocess.PIPE,
+ stderr=asyncio.subprocess.PIPE,
+ cwd="/tmp",
+ )
+ stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
+ output = stdout.decode()[:2000] + (stderr.decode()[:500] if stderr else "")
+ return output or "Command executed (no output)"
+ except asyncio.TimeoutError:
+ return "β οΈ Command timed out after 30s"
+ except Exception as e:
+ return f"β Shell error: {str(e)}"
+
+ # βββ File Tool βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_file(self, task, goal, previous, task_id, session_id) -> str:
+ """Create or modify files."""
+ from core.agent import AgentCore
+ agent = AgentCore(self.ws)
+ messages = [
+ {"role": "system", "content": "Generate file content. Respond with JSON: {\"filename\": \"...\", \"content\": \"...\"}"},
+ {"role": "user", "content": f"Task: {task}\nGoal: {goal}"},
+ ]
+ raw = await agent.llm_stream(messages, task_id=task_id, session_id=session_id)
+ try:
+ import json
+ start = raw.find("{")
+ end = raw.rfind("}") + 1
+ data = json.loads(raw[start:end])
+ filename = data.get("filename", "output.txt")
+ content = data.get("content", raw)
+ path = f"/tmp/workspace/{filename}"
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ with open(path, "w") as f:
+ f.write(content)
+ await self.ws.emit(task_id, "step_progress", {
+ "action": "file_written",
+ "filename": filename,
+ "size": len(content),
+ }, session_id=session_id)
+ return f"β
File written: {filename} ({len(content)} chars)"
+ except Exception as e:
+ return f"File task result: {raw[:500]}"
+
+ # βββ GitHub Tool βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_github(self, task, goal, previous, task_id, session_id) -> str:
+ """Perform GitHub operations."""
+ return f"GitHub: {task}\n(Set GITHUB_TOKEN to enable real GitHub operations)"
+
+ # βββ Memory Tool βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_memory(self, task, goal, previous, task_id, session_id) -> str:
+ """Save/retrieve from memory."""
+ from memory.db import save_memory, search_memory
+ results = await search_memory(task[:50], session_id=session_id)
+ if results:
+ return "\n".join([r["content"][:300] for r in results[:3]])
+ return "No relevant memories found"
+
+ # βββ Search Tool βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_search(self, task, goal, previous, task_id, session_id) -> str:
+ """Web search using available APIs."""
+ return f"Search result for: {task}\n(Integrate search API for real results)"
+
+ # βββ Test Tool βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_test(self, task, goal, previous, task_id, session_id) -> str:
+ """Generate and run tests."""
+ from core.agent import AgentCore
+ agent = AgentCore(self.ws)
+ messages = [
+ {"role": "system", "content": "Write test cases for the given task. Use pytest format."},
+ {"role": "user", "content": f"Write tests for: {task}\nContext: {goal}"},
+ ]
+ result = await agent.llm_stream(messages, task_id=task_id, session_id=session_id)
+ return result or f"# Tests for: {task}"
+
+ # βββ Browser Tool ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_browser(self, task, goal, previous, task_id, session_id) -> str:
+ """Browser automation (stub β extend with playwright)."""
+ return f"Browser task: {task}\n(Install playwright for real browser automation)"
+
+ # βββ None Tool βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+ async def _tool_none(self, task, goal, previous, task_id, session_id) -> str:
+ """Use LLM directly without tools."""
+ from core.agent import AgentCore
+ agent = AgentCore(self.ws)
+ messages = [
+ {"role": "system", "content": "You are an expert engineer. Complete the task thoroughly."},
+ {"role": "user", "content": f"Task: {task}\nGoal context: {goal}"},
+ ]
+ result = await agent.llm_stream(messages, task_id=task_id, session_id=session_id)
+ return result or f"Completed: {task}"
diff --git a/frontend/app/globals.css b/frontend/app/globals.css
new file mode 100644
index 0000000000000000000000000000000000000000..9e464c4f4a7d096c286bec766d4c7d7b01713ce9
--- /dev/null
+++ b/frontend/app/globals.css
@@ -0,0 +1,178 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
+
+:root {
+ --bg-primary: #0f1017;
+ --bg-secondary: #13141c;
+ --bg-tertiary: #1a1b26;
+ --border: #2a2b3d;
+ --text-primary: #e2e8f0;
+ --text-secondary: #94a3b8;
+ --accent: #4f6ef7;
+ --accent-glow: rgba(79, 110, 247, 0.3);
+ --success: #4ade80;
+ --warning: #facc15;
+ --error: #f87171;
+ --terminal-bg: #0a0b10;
+}
+
+* { box-sizing: border-box; margin: 0; padding: 0; }
+
+html, body {
+ height: 100%;
+ background-color: var(--bg-primary);
+ color: var(--text-primary);
+ font-family: 'Inter', system-ui, sans-serif;
+ font-size: 14px;
+ line-height: 1.6;
+ overflow: hidden;
+}
+
+/* Scrollbar */
+::-webkit-scrollbar { width: 4px; height: 4px; }
+::-webkit-scrollbar-track { background: transparent; }
+::-webkit-scrollbar-thumb { background: #2a2b3d; border-radius: 2px; }
+::-webkit-scrollbar-thumb:hover { background: #3a3b5a; }
+
+/* Selection */
+::selection { background: var(--accent-glow); color: var(--text-primary); }
+
+/* Focus */
+*:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
+
+/* Animations */
+@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
+@keyframes shimmer {
+ 0% { background-position: -200% 0; }
+ 100% { background-position: 200% 0; }
+}
+@keyframes scan {
+ 0% { transform: translateY(-100%); }
+ 100% { transform: translateY(100vh); }
+}
+@keyframes fadeSlideIn {
+ from { opacity: 0; transform: translateY(8px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+@keyframes pulseRing {
+ 0% { transform: scale(1); opacity: 1; }
+ 100% { transform: scale(2); opacity: 0; }
+}
+
+/* Cursor blink */
+.cursor-blink::after {
+ content: 'β';
+ animation: blink 1s step-end infinite;
+ color: var(--accent);
+}
+
+/* Loading shimmer */
+.shimmer {
+ background: linear-gradient(90deg, #1a1b26 25%, #2a2b3d 50%, #1a1b26 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+}
+
+/* Glass effect */
+.glass {
+ background: rgba(26, 27, 38, 0.7);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
+ border: 1px solid rgba(42, 43, 61, 0.8);
+}
+
+/* Code blocks */
+pre, code {
+ font-family: 'JetBrains Mono', 'Fira Code', monospace;
+}
+
+/* Prose override for dark */
+.prose-dark {
+ color: var(--text-primary);
+}
+.prose-dark h1, .prose-dark h2, .prose-dark h3 { color: #e2e8f0; }
+.prose-dark code {
+ background: #1a1b26;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-size: 0.85em;
+ color: #c084fc;
+}
+.prose-dark pre {
+ background: #0a0b10 !important;
+ border: 1px solid #2a2b3d;
+ border-radius: 8px;
+}
+.prose-dark blockquote {
+ border-left: 3px solid var(--accent);
+ padding-left: 1rem;
+ color: var(--text-secondary);
+}
+.prose-dark a { color: var(--accent); }
+.prose-dark strong { color: #e2e8f0; }
+.prose-dark hr { border-color: #2a2b3d; }
+.prose-dark ul li::marker { color: var(--accent); }
+.prose-dark table th { background: #1a1b26; }
+.prose-dark table td { border-color: #2a2b3d; }
+
+/* Step status colors */
+.step-running { color: #60a5fa; }
+.step-completed { color: #4ade80; }
+.step-failed { color: #f87171; }
+.step-pending { color: #94a3b8; }
+
+/* Typing indicator */
+.typing-dot {
+ width: 6px; height: 6px;
+ border-radius: 50%;
+ background: var(--accent);
+ animation: pulseDot 1.4s ease-in-out infinite;
+}
+.typing-dot:nth-child(2) { animation-delay: 0.2s; }
+.typing-dot:nth-child(3) { animation-delay: 0.4s; }
+@keyframes pulseDot {
+ 0%, 80%, 100% { transform: scale(0.6); opacity: 0.4; }
+ 40% { transform: scale(1); opacity: 1; }
+}
+
+/* Message animation */
+.message-enter {
+ animation: fadeSlideIn 0.3s ease-out forwards;
+}
+
+/* Status badge */
+.status-queued { background: #1e293b; color: #94a3b8; }
+.status-planning { background: #1a1f3a; color: #818cf8; }
+.status-executing { background: #1a2d3a; color: #38bdf8; }
+.status-completed { background: #162b1e; color: #4ade80; }
+.status-failed { background: #2b1619; color: #f87171; }
+.status-retrying { background: #2b2419; color: #facc15; }
+
+/* Glow border */
+.glow-border {
+ box-shadow: 0 0 0 1px var(--accent), 0 0 12px var(--accent-glow);
+}
+
+/* Terminal window */
+.terminal {
+ background: var(--terminal-bg);
+ border-radius: 8px;
+ border: 1px solid #2a2b3d;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 12px;
+ line-height: 1.5;
+}
+
+/* Scan line effect */
+.scan-line::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 2px;
+ background: linear-gradient(90deg, transparent, var(--accent), transparent);
+ opacity: 0.3;
+ animation: scan 4s linear infinite;
+}
diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..ee9cff972d32aa2b2af8043859cac09faf574671
--- /dev/null
+++ b/frontend/app/layout.tsx
@@ -0,0 +1,21 @@
+import type { Metadata } from 'next'
+import './globals.css'
+
+export const metadata: Metadata = {
+ title: 'π€ Devin Agent β Autonomous AI Engineering Platform',
+ description: 'Production-grade autonomous AI coding agent with real-time streaming, WebSocket execution, GitHub automation, and persistent memory.',
+ keywords: ['AI agent', 'autonomous coding', 'Devin', 'Manus', 'streaming AI'],
+}
+
+export default function RootLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..3d89c57f4dcc644e028d2a6684f4aaccb7c24ba2
--- /dev/null
+++ b/frontend/app/page.tsx
@@ -0,0 +1,65 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { useAgentStore } from '@/hooks/useAgentStore'
+import { useAgentWebSocket } from '@/hooks/useWebSocket'
+import TopBar from '@/components/layout/TopBar'
+import Sidebar from '@/components/layout/Sidebar'
+import ChatPanel from '@/components/chat/ChatPanel'
+import ExecutionTimeline from '@/components/timeline/ExecutionTimeline'
+import TasksPanel from '@/components/layout/TasksPanel'
+import MemoryPanel from '@/components/layout/MemoryPanel'
+
+export default function HomePage() {
+ const { activePanel, activeTaskId } = useAgentStore()
+ const [mounted, setMounted] = useState(false)
+ useEffect(() => setMounted(true), [])
+
+ // Connect to global log stream + active task stream
+ useAgentWebSocket(undefined)
+ useAgentWebSocket(activeTaskId || undefined)
+
+ if (!mounted) return (
+
+
+
π€
+
Loading Devin Agent...
+
+
+
+ )
+
+ const RightPanel = () => {
+ switch (activePanel) {
+ case 'timeline': return
+ case 'tasks': return
+ case 'memory': return
+ default: return
+ }
+ }
+
+ return (
+
+ {/* Top bar */}
+
+
+ {/* Main layout */}
+
+ {/* Left sidebar */}
+
+
+ {/* Center: Chat */}
+
+
+
+
+ {/* Right: Timeline / Tasks / Memory */}
+
+
+
+
+
+ )
+}
diff --git a/frontend/components/chat/ChatPanel.tsx b/frontend/components/chat/ChatPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..66e840f49f3d336f039da4c9cc0e0bf53cf873b1
--- /dev/null
+++ b/frontend/components/chat/ChatPanel.tsx
@@ -0,0 +1,233 @@
+'use client'
+
+import { useState, useRef, useEffect, useCallback } from 'react'
+import { useAgentStore } from '@/hooks/useAgentStore'
+import { streamChatSSE } from '@/lib/websocket'
+import { createTask } from '@/lib/api'
+import MessageBubble from './MessageBubble'
+import { Send, Loader2, Zap, Code2, GitBranch, Brain, Square } from 'lucide-react'
+
+const QUICK_ACTIONS = [
+ { icon: Code2, label: 'Build a REST API', prompt: 'Build a production-ready REST API with FastAPI, SQLite, authentication, and CRUD endpoints for a todo app' },
+ { icon: GitBranch, label: 'Create GitHub repo', prompt: 'Create a new GitHub repository, initialize it with a README, add a .gitignore for Python, and push initial code' },
+ { icon: Brain, label: 'Analyze codebase', prompt: 'Analyze the current project structure and suggest improvements for code quality, performance, and maintainability' },
+ { icon: Zap, label: 'Deploy to Vercel', prompt: 'Deploy this application to Vercel with proper environment variables and generate a production URL' },
+]
+
+export default function ChatPanel() {
+ const [input, setInput] = useState('')
+ const [mode, setMode] = useState<'chat' | 'agent'>('agent')
+ const messagesEndRef = useRef(null)
+ const inputRef = useRef(null)
+ const abortRef = useRef(null)
+
+ const store = useAgentStore()
+ const { messages, sessionId, isStreaming, addMessage, setStreaming, appendChunk, updateMessage } = store
+
+ useEffect(() => {
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
+ }, [messages])
+
+ const handleSubmit = useCallback(async (e?: React.FormEvent) => {
+ e?.preventDefault()
+ const text = input.trim()
+ if (!text || isStreaming) return
+
+ setInput('')
+ inputRef.current?.focus()
+
+ // Add user message
+ addMessage({ role: 'user', content: text })
+
+ if (mode === 'agent') {
+ // Create autonomous task
+ try {
+ const assistantId = addMessage({
+ role: 'assistant',
+ content: '',
+ streaming: true,
+ metadata: { mode: 'agent' },
+ })
+ setStreaming(true, assistantId)
+
+ const result = await createTask(text, sessionId)
+
+ updateMessage(assistantId, {
+ content: `π **Task Created** \`${result.task_id}\`\n\nConnecting to execution stream... Watch the timeline β\n\n**Goal:** ${text}`,
+ streaming: false,
+ metadata: { task_id: result.task_id, mode: 'agent' },
+ })
+ setStreaming(false, null)
+ } catch (err: any) {
+ const id = addMessage({
+ role: 'assistant',
+ content: `β Failed to create task: ${err.message}\n\nMake sure the backend is running at \`${process.env.NEXT_PUBLIC_API_URL}\``,
+ metadata: { error: true },
+ })
+ setStreaming(false, null)
+ }
+ } else {
+ // Streaming chat mode
+ const assistantId = addMessage({
+ role: 'assistant',
+ content: '',
+ streaming: true,
+ metadata: { mode: 'chat' },
+ })
+ setStreaming(true, assistantId)
+
+ const chatMessages = [
+ ...messages.filter(m => !m.streaming).slice(-10).map(m => ({
+ role: m.role as 'user' | 'assistant',
+ content: m.content,
+ })),
+ { role: 'user' as const, content: text },
+ ]
+
+ await streamChatSSE(
+ chatMessages,
+ sessionId,
+ (chunk) => appendChunk(assistantId, chunk),
+ (full) => {
+ updateMessage(assistantId, { content: full, streaming: false })
+ setStreaming(false, null)
+ },
+ (err) => {
+ updateMessage(assistantId, {
+ content: `β Stream error: ${err}`,
+ streaming: false,
+ metadata: { error: true },
+ })
+ setStreaming(false, null)
+ }
+ )
+ }
+ }, [input, isStreaming, mode, messages, sessionId, addMessage, setStreaming, appendChunk, updateMessage])
+
+ const handleKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault()
+ handleSubmit()
+ }
+ }
+
+ const stopStreaming = () => {
+ abortRef.current?.abort()
+ setStreaming(false, null)
+ if (store.streamingMessageId) {
+ updateMessage(store.streamingMessageId, { streaming: false, content: store.messages.find(m => m.id === store.streamingMessageId)?.content + ' [stopped]' })
+ }
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+
Agent Chat
+
{sessionId.slice(0, 12)}...
+
+ {/* Mode switcher */}
+
+ {(['agent', 'chat'] as const).map((m) => (
+
+ ))}
+
+
+
+ {/* Messages */}
+
+ {messages.length === 0 && (
+
+
+
π€
+
Devin Agent
+
+ Autonomous AI engineering platform. Give me a goal and I'll plan, code, and execute it.
+
+
+
+ {QUICK_ACTIONS.map(({ icon: Icon, label, prompt }) => (
+
+ ))}
+
+
+ )}
+
+ {messages.map((msg) => (
+
+ ))}
+
+
+
+ {/* Input */}
+
+
+ )
+}
diff --git a/frontend/components/chat/MessageBubble.tsx b/frontend/components/chat/MessageBubble.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..516ef9e28d40fc3d721358fb74727b39825a9b10
--- /dev/null
+++ b/frontend/components/chat/MessageBubble.tsx
@@ -0,0 +1,162 @@
+'use client'
+
+import { Message } from '@/types'
+import ReactMarkdown from 'react-markdown'
+import remarkGfm from 'remark-gfm'
+import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
+import { oneDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
+import { Copy, Check, Bot, User } from 'lucide-react'
+import { useState, memo } from 'react'
+import { formatDistanceToNow } from 'date-fns'
+
+interface Props { message: Message }
+
+const CopyButton = ({ text }: { text: string }) => {
+ const [copied, setCopied] = useState(false)
+ const copy = () => {
+ navigator.clipboard.writeText(text)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ }
+ return (
+
+ )
+}
+
+const TypingIndicator = () => (
+
+ {[0,1,2].map(i => (
+
+ ))}
+
+)
+
+const MessageBubble = memo(({ message }: Props) => {
+ const isUser = message.role === 'user'
+ const isSystem = message.role === 'system'
+ const isStreaming = message.streaming
+ const isEmpty = !message.content && isStreaming
+
+ const time = formatDistanceToNow(new Date(message.timestamp * 1000), { addSuffix: true })
+
+ if (isSystem) {
+ return (
+
+
+ {message.content}
+
+
+ )
+ }
+
+ return (
+
+ {/* Avatar */}
+
+ {isUser
+ ?
+ :
+ }
+
+
+ {/* Bubble */}
+
+
+ {isEmpty ? (
+
+ ) : (
+
+
+
+ {match[1]}
+
+
+
+ {code}
+
+
+ ) : (
+
+ {children}
+
+ )
+ },
+ p: ({ children }) =>
{children}
,
+ ul: ({ children }) =>
,
+ ol: ({ children }) =>
{children}
,
+ li: ({ children }) =>
{children},
+ h1: ({ children }) =>
{children}
,
+ h2: ({ children }) =>
{children}
,
+ h3: ({ children }) =>
{children}
,
+ blockquote: ({ children }) => (
+
{children}
+ ),
+ strong: ({ children }) =>
{children},
+ a: ({ href, children }) => (
+
{children}
+ ),
+ table: ({ children }) => (
+
+ ),
+ th: ({ children }) =>
{children} | ,
+ td: ({ children }) =>
{children} | ,
+ hr: () =>
,
+ }}
+ >
+ {message.content}
+
+
+ )}
+
+
+ {/* Metadata row */}
+
+ {time}
+ {message.metadata?.task_id && (
+
+ {message.metadata.task_id}
+
+ )}
+ {isStreaming && (
+
+
+ streaming
+
+ )}
+
+
+
+ )
+})
+MessageBubble.displayName = 'MessageBubble'
+export default MessageBubble
diff --git a/frontend/components/layout/MemoryPanel.tsx b/frontend/components/layout/MemoryPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8d9f2b32a11e54903e856ed3a56c9244720e917d
--- /dev/null
+++ b/frontend/components/layout/MemoryPanel.tsx
@@ -0,0 +1,103 @@
+'use client'
+
+import { useAgentStore } from '@/hooks/useAgentStore'
+import { searchMemory } from '@/lib/api'
+import { useState } from 'react'
+import { Search, Brain, Loader2 } from 'lucide-react'
+import { formatDistanceToNow } from 'date-fns'
+
+const TYPE_COLORS: Record = {
+ conversation: 'text-blue-400 bg-blue-400/10',
+ task: 'text-green-400 bg-green-400/10',
+ project: 'text-purple-400 bg-purple-400/10',
+ execution: 'text-cyan-400 bg-cyan-400/10',
+ tool: 'text-yellow-400 bg-yellow-400/10',
+ error: 'text-red-400 bg-red-400/10',
+ repo: 'text-orange-400 bg-orange-400/10',
+ planning: 'text-indigo-400 bg-indigo-400/10',
+}
+
+export default function MemoryPanel() {
+ const { sessionId } = useAgentStore()
+ const [query, setQuery] = useState('')
+ const [results, setResults] = useState([])
+ const [loading, setLoading] = useState(false)
+
+ const handleSearch = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!query.trim()) return
+ setLoading(true)
+ try {
+ const data = await searchMemory(query, sessionId)
+ setResults(data.results || [])
+ } catch {
+ setResults([])
+ } finally {
+ setLoading(false)
+ }
+ }
+
+ return (
+
+
+
+ {/* Search */}
+
+
+
+
+ {/* Results */}
+
+ {results.length === 0 && (
+
+
+
Search agent memories
+
Tasks, conversations, code, plans
+
+ )}
+ {results.map((mem) => {
+ const typeStyle = TYPE_COLORS[mem.memory_type] || 'text-slate-400 bg-slate-400/10'
+ const time = formatDistanceToNow(new Date(mem.created_at * 1000), { addSuffix: true })
+ return (
+
+
+
+ {mem.memory_type}
+
+ {time}
+
+
+ {mem.content}
+
+ {mem.key && (
+
{mem.key}
+ )}
+
+ )
+ })}
+
+
+ )
+}
diff --git a/frontend/components/layout/Sidebar.tsx b/frontend/components/layout/Sidebar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..f0a88d47903bc73a9711973df5678544c2958984
--- /dev/null
+++ b/frontend/components/layout/Sidebar.tsx
@@ -0,0 +1,193 @@
+'use client'
+
+import { useAgentStore } from '@/hooks/useAgentStore'
+import { Task } from '@/types'
+import { formatDistanceToNow } from 'date-fns'
+import {
+ MessageSquare, Clock, ListTodo, Brain, Settings,
+ Plus, ChevronLeft, ChevronRight, Circle, Wifi, WifiOff,
+ RefreshCcw, Trash2
+} from 'lucide-react'
+import { retryTask, cancelTask } from '@/lib/api'
+
+const STATUS_DOT: Record = {
+ queued: 'bg-slate-500',
+ initializing: 'bg-blue-400 animate-pulse',
+ planning: 'bg-purple-400 animate-pulse',
+ executing: 'bg-blue-400 animate-pulse',
+ streaming: 'bg-cyan-400 animate-pulse',
+ retrying: 'bg-yellow-400 animate-pulse',
+ completed: 'bg-green-400',
+ failed: 'bg-red-400',
+ cancelled: 'bg-slate-600',
+}
+
+function TaskItem({ task }: { task: Task }) {
+ const store = useAgentStore()
+ const isActive = store.activeTaskId === task.id
+ const time = formatDistanceToNow(new Date(task.created_at * 1000), { addSuffix: true })
+
+ return (
+
+ )
+}
+
+export default function Sidebar() {
+ const store = useAgentStore()
+ const { sidebarOpen, setSidebarOpen, activePanel, setActivePanel, tasks, wsConnected, wsRetries, clearMessages, clearTimeline } = store
+
+ const NAV_ITEMS = [
+ { id: 'chat' as const, icon: MessageSquare, label: 'Chat' },
+ { id: 'timeline' as const, icon: Clock, label: 'Timeline' },
+ { id: 'tasks' as const, icon: ListTodo, label: 'Tasks' },
+ { id: 'memory' as const, icon: Brain, label: 'Memory' },
+ ]
+
+ const runningTasks = tasks.filter(t => ['executing', 'planning', 'retrying'].includes(t.status))
+ const recentTasks = tasks.slice(0, 15)
+
+ return (
+ <>
+ {/* Collapsed sidebar β icon rail */}
+
+ {/* Logo + Toggle */}
+
+ {sidebarOpen && (
+
+
+ D
+
+
+
Devin Agent
+
v2.0 Production
+
+
+ )}
+
setSidebarOpen(!sidebarOpen)}
+ className="p-1 rounded hover:bg-[#1a1b26] text-slate-500 hover:text-slate-300 transition-all ml-auto"
+ >
+ {sidebarOpen ? : }
+
+
+
+ {/* Nav items */}
+
+
+ {sidebarOpen && (
+ <>
+
+
+ {/* Active tasks */}
+ {runningTasks.length > 0 && (
+
+
+
+
+ Running ({runningTasks.length})
+
+
+
+ {runningTasks.map(t => )}
+
+
+ )}
+
+ {/* Recent tasks */}
+ {recentTasks.length > 0 && (
+
+
+
+ Recent Tasks
+
+ {recentTasks.length}
+
+
+ {recentTasks.map(t => )}
+
+
+ )}
+ >
+ )}
+
+ {/* Connection status + actions */}
+
+ {sidebarOpen ? (
+
+
+ {wsConnected
+ ?
+ :
+ }
+
+ {wsConnected ? 'Connected' : `Reconnecting${wsRetries > 0 ? ` (${wsRetries})` : ''}`}
+
+
+
{ clearMessages(); clearTimeline() }}
+ title="Clear session"
+ className="p-1 rounded hover:bg-[#1a1b26] text-slate-600 hover:text-slate-400 transition-all"
+ >
+
+
+
+ ) : (
+
+ {wsConnected
+ ?
+ :
+ }
+
+ )}
+
+
+ >
+ )
+}
diff --git a/frontend/components/layout/TasksPanel.tsx b/frontend/components/layout/TasksPanel.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9c3b4e2450e0dd4abae4615dbaeafc37c871887f
--- /dev/null
+++ b/frontend/components/layout/TasksPanel.tsx
@@ -0,0 +1,170 @@
+'use client'
+
+import { useAgentStore } from '@/hooks/useAgentStore'
+import { Task } from '@/types'
+import { formatDistanceToNow } from 'date-fns'
+import { RefreshCcw, XCircle, ChevronDown, ChevronUp, Terminal } from 'lucide-react'
+import { useState } from 'react'
+import { retryTask, cancelTask } from '@/lib/api'
+
+const STATUS_BADGE: Record = {
+ queued: 'bg-slate-800 text-slate-400 border-slate-700',
+ initializing: 'bg-blue-900/30 text-blue-400 border-blue-700/30',
+ planning: 'bg-purple-900/30 text-purple-400 border-purple-700/30',
+ executing: 'bg-cyan-900/30 text-cyan-400 border-cyan-700/30',
+ retrying: 'bg-yellow-900/30 text-yellow-400 border-yellow-700/30',
+ completed: 'bg-green-900/30 text-green-400 border-green-700/30',
+ failed: 'bg-red-900/30 text-red-400 border-red-700/30',
+ cancelled: 'bg-slate-800 text-slate-500 border-slate-700',
+}
+
+function TaskCard({ task }: { task: Task }) {
+ const [expanded, setExpanded] = useState(false)
+ const store = useAgentStore()
+ const isActive = store.activeTaskId === task.id
+ const time = formatDistanceToNow(new Date(task.created_at * 1000), { addSuffix: true })
+ const duration = task.completed_at && task.started_at
+ ? `${Math.round(task.completed_at - task.started_at)}s`
+ : null
+
+ return (
+
+
{ store.setActiveTask(task.id); setExpanded(!expanded) }}
+ >
+
+
+
{task.goal}
+
+
+ {task.status}
+
+ {task.id.slice(0, 14)}
+ {duration && β± {duration}}
+ {task.retry_count > 0 && (
+ β» {task.retry_count}
+ )}
+
+
+
+ {task.status === 'failed' && (
+ { e.stopPropagation(); retryTask(task.id) }}
+ className="p-1.5 rounded-lg bg-yellow-500/10 hover:bg-yellow-500/20 text-yellow-400 transition-all"
+ title="Retry"
+ >
+
+
+ )}
+ {['queued','executing','planning'].includes(task.status) && (
+ { e.stopPropagation(); cancelTask(task.id) }}
+ className="p-1.5 rounded-lg bg-red-500/10 hover:bg-red-500/20 text-red-400 transition-all"
+ title="Cancel"
+ >
+
+
+ )}
+ {expanded ? : }
+
+
+
+
+ {expanded && (
+
+ {/* Plan steps */}
+ {task.plan?.steps && task.plan.steps.length > 0 && (
+
+
π Plan ({task.plan.steps.length} steps)
+
+ {task.plan.steps.map((step, i) => (
+
+ {i+1}.
+ {step.name}
+ {step.tool && {step.tool}}
+
+ ))}
+
+
+ )}
+
+ {/* Result */}
+ {task.result && (
+
+
β
Result
+
+ {task.result.slice(0, 500)}
+
+
+ )}
+
+ {/* Error */}
+ {task.error && (
+
+
β Error
+
+ {task.error}
+
+
+ )}
+
+
+ Created {time}
+ {task.session_id && sess: {task.session_id.slice(0,10)}}
+
+
+ )}
+
+ )
+}
+
+export default function TasksPanel() {
+ const { tasks } = useAgentStore()
+ const active = tasks.filter(t => ['queued','initializing','planning','executing','retrying'].includes(t.status))
+ const done = tasks.filter(t => ['completed','failed','cancelled'].includes(t.status))
+
+ return (
+
+
+ Task Manager
+ {tasks.length} total
+
+
+ {tasks.length === 0 ? (
+
+
+
No tasks yet
+
Submit a goal to create tasks
+
+ ) : (
+ <>
+ {active.length > 0 && (
+
+
+ Active ({active.length})
+
+
{active.map(t => )}
+
+ )}
+ {done.length > 0 && (
+
+
+ Completed ({done.length})
+
+
{done.map(t => )}
+
+ )}
+ >
+ )}
+
+
+ )
+}
diff --git a/frontend/components/layout/TopBar.tsx b/frontend/components/layout/TopBar.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..03a41a5d79254735cf87983497875530c2cda733
--- /dev/null
+++ b/frontend/components/layout/TopBar.tsx
@@ -0,0 +1,98 @@
+'use client'
+
+import { useAgentStore } from '@/hooks/useAgentStore'
+import { useEffect, useState } from 'react'
+import { getHealth } from '@/lib/api'
+import { Activity, Cpu, MemoryStick, Wifi, WifiOff, Github, ExternalLink } from 'lucide-react'
+
+export default function TopBar() {
+ const { wsConnected, backendHealth, setBackendHealth, sessionId } = useAgentStore()
+ const [metrics, setMetrics] = useState(null)
+
+ useEffect(() => {
+ const fetchHealth = async () => {
+ const h = await getHealth()
+ setBackendHealth(h)
+ }
+ fetchHealth()
+ const interval = setInterval(fetchHealth, 30000)
+ return () => clearInterval(interval)
+ }, [])
+
+ return (
+
+ {/* Brand */}
+
+
D
+
Devin Agent Platform
+
v2.0
+
+
+
+
+ {/* Backend Status */}
+
+
+ {backendHealth ? (
+
+ ) : (
+
+ )}
+
+ {backendHealth ? 'API Online' : 'API Offline'}
+
+
+
+ {/* WS Status */}
+
+ {wsConnected
+ ?
+ :
+ }
+
+ {wsConnected ? 'WS Live' : 'WS Off'}
+
+
+
+ {/* LLM status */}
+ {backendHealth?.llm && (
+
+
+
+ {backendHealth.llm.openai ? 'GPT-4' : backendHealth.llm.anthropic ? 'Claude' : 'Demo Mode'}
+
+
+ )}
+
+
+ {/* Spacer */}
+
+
+ {/* Session ID */}
+
+ Session:
+ {sessionId.slice(0, 14)}
+
+
+ {/* GitHub */}
+ {backendHealth?.github && (
+
+
+ GitHub
+
+ )}
+
+ {/* Docs link */}
+
+
+ API
+
+
+ )
+}
diff --git a/frontend/components/timeline/ExecutionTimeline.tsx b/frontend/components/timeline/ExecutionTimeline.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e1b2addd6cc11612af555c6967db86b99d6d2e64
--- /dev/null
+++ b/frontend/components/timeline/ExecutionTimeline.tsx
@@ -0,0 +1,224 @@
+'use client'
+
+import { useAgentStore } from '@/hooks/useAgentStore'
+import { TimelineEvent, TaskStep } from '@/types'
+import { formatDistanceToNow } from 'date-fns'
+import { memo, useState } from 'react'
+import {
+ CheckCircle, XCircle, Clock, Loader2, AlertTriangle,
+ ChevronDown, ChevronRight, Code2, Terminal, GitBranch,
+ Brain, Search, TestTube, Globe, Zap, Database
+} from 'lucide-react'
+
+const TOOL_ICONS: Record = {
+ code: Code2, shell: Terminal, github: GitBranch, memory: Brain,
+ search: Search, test: TestTube, browser: Globe, file: Database,
+ none: Zap,
+}
+
+const STATUS_STYLES: Record = {
+ running: 'text-blue-400 bg-blue-400/10 border-blue-400/30',
+ completed: 'text-green-400 bg-green-400/10 border-green-400/30',
+ failed: 'text-red-400 bg-red-400/10 border-red-400/30',
+ warning: 'text-yellow-400 bg-yellow-400/10 border-yellow-400/30',
+ pending: 'text-slate-500 bg-slate-500/10 border-slate-500/30',
+}
+
+const StatusIcon = ({ status }: { status: string }) => {
+ if (status === 'running') return
+ if (status === 'completed') return
+ if (status === 'failed') return
+ if (status === 'warning') return
+ return
+}
+
+const TimelineItem = memo(({ event, isLast }: { event: TimelineEvent; isLast: boolean }) => {
+ const [expanded, setExpanded] = useState(false)
+ const ToolIcon = event.tool ? (TOOL_ICONS[event.tool] || Zap) : null
+ const hasData = event.data && Object.keys(event.data).length > 0
+ const time = formatDistanceToNow(new Date(event.timestamp * 1000), { addSuffix: true })
+
+ return (
+
+ {/* Connector line */}
+
+
+ {/* Content */}
+
+
hasData && setExpanded(!expanded)}
+ >
+
+
+ {ToolIcon && (
+
+
+
+ )}
+ {event.label}
+
+
+ {time}
+ {hasData && (
+ expanded
+ ?
+ :
+ )}
+
+
+
+ {event.description && (
+
{event.description}
+ )}
+
+ {/* Expanded data */}
+ {expanded && hasData && (
+
+
+ {JSON.stringify(event.data, null, 2)}
+
+
+ )}
+
+
+
+ )
+})
+TimelineItem.displayName = 'TimelineItem'
+
+// βββ Step Progress Bar ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+const StepProgress = ({ steps }: { steps: TaskStep[] }) => {
+ if (!steps.length) return null
+ const completed = steps.filter(s => s.status === 'completed').length
+ const percent = Math.round((completed / steps.length) * 100)
+
+ return (
+
+
+ Execution Progress
+ {completed}/{steps.length} steps
+
+
+
+ {steps.map((step) => (
+
+ ))}
+
+
+ )
+}
+
+// βββ Main Timeline Component ββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+export default function ExecutionTimeline() {
+ const { timeline, activeSteps, activeTaskId, tasks, clearTimeline } = useAgentStore()
+ const activeTask = tasks.find(t => t.id === activeTaskId)
+
+ const getStatusBadge = (status: string) => {
+ const styles: Record = {
+ queued: 'status-queued',
+ planning: 'status-planning',
+ executing: 'status-executing',
+ completed: 'status-completed',
+ failed: 'status-failed',
+ retrying: 'status-retrying',
+ }
+ return styles[status] || 'status-queued'
+ }
+
+ return (
+
+ {/* Header */}
+
+
+ Execution Timeline
+ {activeTask && (
+
+ {activeTask.status}
+
+ )}
+
+
+ {timeline.length} events
+ {timeline.length > 0 && (
+
+ Clear
+
+ )}
+
+
+
+ {/* Active task info */}
+ {activeTask && (
+
+
+ Goal: {activeTask.goal.slice(0, 100)}
+
+ {activeTask.retry_count > 0 && (
+
β» Retry #{activeTask.retry_count}
+ )}
+
+ )}
+
+ {/* Step progress */}
+
+
+ {/* Timeline events */}
+
+ {timeline.length === 0 ? (
+
+
β±οΈ
+
No events yet
+
Submit a task to see live execution
+
+ ) : (
+
+ {[...timeline].reverse().map((event, i) => (
+
+ ))}
+
+ )}
+
+
+ {/* Active task result */}
+ {activeTask?.result && (
+
+
β Result
+
{activeTask.result}
+
+ )}
+
+ )
+}
diff --git a/frontend/ecosystem.config.cjs b/frontend/ecosystem.config.cjs
new file mode 100644
index 0000000000000000000000000000000000000000..351256be525a0ed21f211adaf44455ac828741ee
--- /dev/null
+++ b/frontend/ecosystem.config.cjs
@@ -0,0 +1,19 @@
+module.exports = {
+ apps: [
+ {
+ name: 'devin-frontend',
+ script: 'npm',
+ args: 'start',
+ cwd: '/home/user/devin-agent/frontend',
+ watch: false,
+ instances: 1,
+ exec_mode: 'fork',
+ env: {
+ PORT: 3000,
+ NODE_ENV: 'production',
+ NEXT_PUBLIC_API_URL: 'http://localhost:7860',
+ NEXT_PUBLIC_WS_URL: 'ws://localhost:7860',
+ },
+ },
+ ],
+}
diff --git a/frontend/hooks/nanoid.ts b/frontend/hooks/nanoid.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b0ccc40baa7f981fee7b2a11a7dbc53c195426d1
--- /dev/null
+++ b/frontend/hooks/nanoid.ts
@@ -0,0 +1,8 @@
+export function nanoid(size = 10): string {
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
+ let result = ''
+ for (let i = 0; i < size; i++) {
+ result += chars.charAt(Math.floor(Math.random() * chars.length))
+ }
+ return result
+}
diff --git a/frontend/hooks/useAgentStore.ts b/frontend/hooks/useAgentStore.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e68bf4561da7a066794fe28b98b80b040dc374aa
--- /dev/null
+++ b/frontend/hooks/useAgentStore.ts
@@ -0,0 +1,149 @@
+// βββ Zustand Global State Store βββββββββββββββββββββββββββββββββββββββββββββββ
+
+import { create } from 'zustand'
+import { Message, Task, TimelineEvent, AgentSession, TaskStep } from '@/types'
+import { nanoid } from './nanoid'
+
+interface AgentStore {
+ // Session
+ sessionId: string
+ projectId: string
+ setSessionId: (id: string) => void
+ setProjectId: (id: string) => void
+
+ // Messages (chat panel)
+ messages: Message[]
+ addMessage: (msg: Omit) => string
+ updateMessage: (id: string, updates: Partial) => void
+ appendChunk: (id: string, chunk: string) => void
+ clearMessages: () => void
+
+ // Tasks
+ tasks: Task[]
+ activeTaskId: string | null
+ setActiveTask: (id: string | null) => void
+ addTask: (task: Task) => void
+ updateTask: (id: string, updates: Partial) => void
+ getTask: (id: string) => Task | undefined
+
+ // Timeline events
+ timeline: TimelineEvent[]
+ addTimelineEvent: (event: Omit) => void
+ updateTimelineEvent: (id: string, updates: Partial) => void
+ clearTimeline: () => void
+
+ // Active steps
+ activeSteps: TaskStep[]
+ setActiveSteps: (steps: TaskStep[]) => void
+ updateActiveStep: (name: string, updates: Partial) => void
+
+ // Connection status
+ wsConnected: boolean
+ wsRetries: number
+ setWsConnected: (connected: boolean, retries?: number) => void
+
+ // UI state
+ sidebarOpen: boolean
+ activePanel: 'chat' | 'timeline' | 'tasks' | 'memory'
+ setSidebarOpen: (open: boolean) => void
+ setActivePanel: (panel: 'chat' | 'timeline' | 'tasks' | 'memory') => void
+
+ // Streaming
+ isStreaming: boolean
+ streamingMessageId: string | null
+ setStreaming: (active: boolean, msgId?: string | null) => void
+
+ // Backend health
+ backendHealth: any
+ setBackendHealth: (health: any) => void
+}
+
+function uid() {
+ return Math.random().toString(36).slice(2, 10)
+}
+
+export const useAgentStore = create((set, get) => ({
+ sessionId: `sess_${uid()}`,
+ projectId: '',
+ setSessionId: (id) => set({ sessionId: id }),
+ setProjectId: (id) => set({ projectId: id }),
+
+ messages: [],
+ addMessage: (msg) => {
+ const id = uid()
+ const full: Message = {
+ ...msg,
+ id,
+ timestamp: Date.now() / 1000,
+ }
+ set((s) => ({ messages: [...s.messages, full] }))
+ return id
+ },
+ updateMessage: (id, updates) =>
+ set((s) => ({
+ messages: s.messages.map((m) => (m.id === id ? { ...m, ...updates } : m)),
+ })),
+ appendChunk: (id, chunk) =>
+ set((s) => ({
+ messages: s.messages.map((m) =>
+ m.id === id ? { ...m, content: m.content + chunk } : m
+ ),
+ })),
+ clearMessages: () => set({ messages: [] }),
+
+ tasks: [],
+ activeTaskId: null,
+ setActiveTask: (id) => set({ activeTaskId: id }),
+ addTask: (task) =>
+ set((s) => ({
+ tasks: [task, ...s.tasks.filter((t) => t.id !== task.id)],
+ })),
+ updateTask: (id, updates) =>
+ set((s) => ({
+ tasks: s.tasks.map((t) => (t.id === id ? { ...t, ...updates } : t)),
+ })),
+ getTask: (id) => get().tasks.find((t) => t.id === id),
+
+ timeline: [],
+ addTimelineEvent: (event) => {
+ const id = uid()
+ set((s) => ({
+ timeline: [
+ ...s.timeline,
+ { ...event, id },
+ ],
+ }))
+ },
+ updateTimelineEvent: (id, updates) =>
+ set((s) => ({
+ timeline: s.timeline.map((e) => (e.id === id ? { ...e, ...updates } : e)),
+ })),
+ clearTimeline: () => set({ timeline: [] }),
+
+ activeSteps: [],
+ setActiveSteps: (steps) => set({ activeSteps: steps }),
+ updateActiveStep: (name, updates) =>
+ set((s) => ({
+ activeSteps: s.activeSteps.map((st) =>
+ st.name === name ? { ...st, ...updates } : st
+ ),
+ })),
+
+ wsConnected: false,
+ wsRetries: 0,
+ setWsConnected: (connected, retries = 0) =>
+ set({ wsConnected: connected, wsRetries: retries }),
+
+ sidebarOpen: true,
+ activePanel: 'timeline',
+ setSidebarOpen: (open) => set({ sidebarOpen: open }),
+ setActivePanel: (panel) => set({ activePanel: panel }),
+
+ isStreaming: false,
+ streamingMessageId: null,
+ setStreaming: (active, msgId = null) =>
+ set({ isStreaming: active, streamingMessageId: msgId }),
+
+ backendHealth: null,
+ setBackendHealth: (health) => set({ backendHealth: health }),
+}))
diff --git a/frontend/hooks/useWebSocket.ts b/frontend/hooks/useWebSocket.ts
new file mode 100644
index 0000000000000000000000000000000000000000..48d43e795390ea3cf7082bacb14ca0d068529b6a
--- /dev/null
+++ b/frontend/hooks/useWebSocket.ts
@@ -0,0 +1,269 @@
+// βββ WebSocket Hook β handles agent events, updates store βββββββββββββββββββββ
+
+'use client'
+
+import { useEffect, useRef, useCallback } from 'react'
+import { AgentWebSocket } from '@/lib/websocket'
+import { useAgentStore } from './useAgentStore'
+import { StreamEvent } from '@/types'
+
+export function useAgentWebSocket(taskId?: string) {
+ const wsRef = useRef(null)
+ const store = useAgentStore()
+
+ const handleEvent = useCallback((event: StreamEvent) => {
+ const { type, data, task_id } = event
+
+ switch (type) {
+ case 'connected':
+ break
+
+ case 'heartbeat':
+ break
+
+ case 'task_created':
+ case 'task_queued':
+ if (task_id) {
+ store.addTask({
+ id: task_id,
+ goal: data.goal || '',
+ status: type === 'task_created' ? 'queued' : 'queued',
+ session_id: event.session_id || store.sessionId,
+ project_id: store.projectId,
+ created_at: event.timestamp,
+ retry_count: 0,
+ ws_url: data.ws_url,
+ stream_url: data.stream_url,
+ })
+ store.setActiveTask(task_id)
+ store.addTimelineEvent({
+ type,
+ label: type === 'task_created' ? 'π Task Created' : 'π Task Queued',
+ description: data.goal ? `Goal: ${data.goal.slice(0, 80)}` : undefined,
+ timestamp: event.timestamp,
+ status: 'completed',
+ data,
+ })
+ }
+ break
+
+ case 'task_started':
+ if (task_id) {
+ store.updateTask(task_id, { status: 'initializing', started_at: event.timestamp })
+ store.addTimelineEvent({
+ type,
+ label: 'π Task Started',
+ description: 'Initializing agent...',
+ timestamp: event.timestamp,
+ status: 'running',
+ data,
+ })
+ }
+ break
+
+ case 'plan_generated':
+ if (task_id) {
+ store.updateTask(task_id, { status: 'executing', plan: data as any })
+ if (data.steps) {
+ store.setActiveSteps(data.steps.map((s: any) => ({
+ id: s.id || Math.random().toString(36).slice(2),
+ name: s.name,
+ description: s.description || '',
+ tool: s.tool,
+ status: 'pending' as const,
+ })))
+ }
+ store.addTimelineEvent({
+ type,
+ label: 'πΊοΈ Plan Generated',
+ description: `${data.steps?.length || 0} steps planned`,
+ timestamp: event.timestamp,
+ status: 'completed',
+ data,
+ })
+ }
+ break
+
+ case 'step_started': {
+ if (task_id) {
+ store.updateTask(task_id, { status: data.status === 'planning' ? 'planning' : 'executing' })
+ store.updateActiveStep(data.step, { status: 'running', started_at: event.timestamp })
+ store.addTimelineEvent({
+ type,
+ label: `βΆ ${data.step || 'Step'}`,
+ description: data.description || data.tool ? `Tool: ${data.tool}` : undefined,
+ timestamp: event.timestamp,
+ status: 'running',
+ tool: data.tool,
+ data,
+ })
+ }
+ break
+ }
+
+ case 'step_progress':
+ store.addTimelineEvent({
+ type,
+ label: `β‘ ${data.action || 'Progress'}`,
+ description: data.command?.slice(0, 100) || data.description || '',
+ timestamp: event.timestamp,
+ status: 'running',
+ data,
+ })
+ break
+
+ case 'tool_called':
+ store.addTimelineEvent({
+ type,
+ label: `π§ Tool: ${data.tool || 'unknown'}`,
+ description: data.description?.slice(0, 120) || data.step || '',
+ timestamp: event.timestamp,
+ status: 'running',
+ tool: data.tool,
+ data,
+ })
+ break
+
+ case 'tool_result':
+ store.addTimelineEvent({
+ type,
+ label: `β
Tool Result: ${data.tool || 'unknown'}`,
+ description: data.success === false
+ ? `Error: ${data.error?.slice(0, 100)}`
+ : data.result?.slice(0, 120) || 'Success',
+ timestamp: event.timestamp,
+ status: data.success === false ? 'failed' : 'completed',
+ tool: data.tool,
+ data,
+ })
+ break
+
+ case 'llm_chunk':
+ // Handled directly by chat panel streaming message
+ if (store.streamingMessageId) {
+ store.appendChunk(store.streamingMessageId, data.chunk || '')
+ }
+ break
+
+ case 'memory_updated':
+ store.addTimelineEvent({
+ type,
+ label: `π§ Memory Updated`,
+ description: `Type: ${data.type || 'unknown'}`,
+ timestamp: event.timestamp,
+ status: 'completed',
+ data,
+ })
+ break
+
+ case 'retry_attempt':
+ if (task_id) {
+ store.updateTask(task_id, { status: 'retrying', retry_count: data.count || 1 })
+ store.addTimelineEvent({
+ type,
+ label: `π Retry #${data.count || 1}`,
+ timestamp: event.timestamp,
+ status: 'warning',
+ data,
+ })
+ }
+ break
+
+ case 'step_completed':
+ if (task_id) {
+ store.updateActiveStep(data.step, { status: 'completed', completed_at: event.timestamp })
+ store.addTimelineEvent({
+ type,
+ label: `β ${data.step || 'Step'} Done`,
+ description: data.output?.slice(0, 100),
+ timestamp: event.timestamp,
+ status: 'completed',
+ data,
+ })
+ }
+ break
+
+ case 'warning':
+ store.addTimelineEvent({
+ type,
+ label: `β οΈ Warning`,
+ description: data.message || data.warning || '',
+ timestamp: event.timestamp,
+ status: 'warning',
+ data,
+ })
+ break
+
+ case 'error':
+ store.addTimelineEvent({
+ type,
+ label: `β Error`,
+ description: data.error?.slice(0, 120) || 'Unknown error',
+ timestamp: event.timestamp,
+ status: 'failed',
+ data,
+ })
+ break
+
+ case 'task_completed':
+ if (task_id) {
+ store.updateTask(task_id, { status: 'completed', result: data.result, completed_at: event.timestamp })
+ store.addTimelineEvent({
+ type,
+ label: 'π Task Completed',
+ description: `${data.steps_completed || 0} steps finished`,
+ timestamp: event.timestamp,
+ status: 'completed',
+ data,
+ })
+ store.setStreaming(false, null)
+ // Add result to chat
+ if (data.result) {
+ store.updateMessage(store.streamingMessageId || '', {
+ content: data.result,
+ streaming: false,
+ metadata: { task_id, completed: true },
+ })
+ }
+ }
+ break
+
+ case 'task_failed':
+ if (task_id) {
+ store.updateTask(task_id, { status: 'failed', error: data.error, completed_at: event.timestamp })
+ store.addTimelineEvent({
+ type,
+ label: 'β Task Failed',
+ description: data.error?.slice(0, 100) || data.reason || 'Failed',
+ timestamp: event.timestamp,
+ status: 'failed',
+ data,
+ })
+ store.setStreaming(false, null)
+ }
+ break
+ }
+ }, [store])
+
+ useEffect(() => {
+ const path = taskId ? `/ws/tasks/${taskId}` : `/ws/logs`
+
+ wsRef.current = new AgentWebSocket(path, {
+ onEvent: handleEvent,
+ onConnect: () => store.setWsConnected(true, 0),
+ onDisconnect: () => store.setWsConnected(false),
+ onError: () => store.setWsConnected(false, wsRef.current?.getRetryCount() || 0),
+ })
+ wsRef.current.connect()
+
+ return () => {
+ wsRef.current?.disconnect()
+ }
+ }, [taskId])
+
+ return {
+ send: (data: object) => wsRef.current?.send(data),
+ isConnected: store.wsConnected,
+ retries: store.wsRetries,
+ }
+}
diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ad890c5a3d18b2567aa48220fffcdad370986f16
--- /dev/null
+++ b/frontend/lib/api.ts
@@ -0,0 +1,122 @@
+// βββ API Client Library ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:7860'
+const WS_URL = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:7860'
+
+export const getApiUrl = () => API_URL
+export const getWsUrl = () => WS_URL
+
+// βββ Task API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+export async function createTask(goal: string, sessionId: string, projectId = '') {
+ const res = await fetch(`${API_URL}/api/v1/tasks/create`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ goal, session_id: sessionId, project_id: projectId, stream: true }),
+ })
+ if (!res.ok) throw new Error(`Create task failed: ${res.statusText}`)
+ return res.json()
+}
+
+export async function getTask(taskId: string) {
+ const res = await fetch(`${API_URL}/api/v1/tasks/${taskId}`)
+ if (!res.ok) throw new Error(`Get task failed: ${res.statusText}`)
+ return res.json()
+}
+
+export async function getTaskStatus(taskId: string) {
+ const res = await fetch(`${API_URL}/api/v1/tasks/${taskId}/status`)
+ if (!res.ok) throw new Error(`Get status failed: ${res.statusText}`)
+ return res.json()
+}
+
+export async function cancelTask(taskId: string, reason = 'User cancelled') {
+ const res = await fetch(`${API_URL}/api/v1/tasks/${taskId}/cancel`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ reason }),
+ })
+ if (!res.ok) throw new Error(`Cancel failed: ${res.statusText}`)
+ return res.json()
+}
+
+export async function retryTask(taskId: string) {
+ const res = await fetch(`${API_URL}/api/v1/tasks/${taskId}/retry`, {
+ method: 'POST',
+ })
+ if (!res.ok) throw new Error(`Retry failed: ${res.statusText}`)
+ return res.json()
+}
+
+export async function listTasks(sessionId = '') {
+ const url = sessionId
+ ? `${API_URL}/api/v1/tasks/?session_id=${sessionId}`
+ : `${API_URL}/api/v1/tasks/`
+ const res = await fetch(url)
+ if (!res.ok) throw new Error(`List tasks failed: ${res.statusText}`)
+ return res.json()
+}
+
+export async function generatePlan(goal: string, sessionId: string) {
+ const res = await fetch(`${API_URL}/api/v1/plan`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ goal, session_id: sessionId, stream: false }),
+ })
+ if (!res.ok) throw new Error(`Plan failed: ${res.statusText}`)
+ return res.json()
+}
+
+// βββ Chat API (non-streaming) βββββββββββββββββββββββββββββββββββββββββββββββββ
+
+export async function sendChatMessage(messages: any[], sessionId: string, stream = false) {
+ const res = await fetch(`${API_URL}/api/v1/chat`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ messages, session_id: sessionId, stream }),
+ })
+ if (!res.ok) throw new Error(`Chat failed: ${res.statusText}`)
+ return res.json()
+}
+
+// βββ GitHub API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+export async function getGitHubStatus() {
+ try {
+ const res = await fetch(`${API_URL}/api/v1/github/status`)
+ if (!res.ok) return { configured: false }
+ return res.json()
+ } catch {
+ return { configured: false }
+ }
+}
+
+// βββ Health API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+export async function getHealth() {
+ try {
+ const res = await fetch(`${API_URL}/api/v1/health`)
+ if (!res.ok) return null
+ return res.json()
+ } catch {
+ return null
+ }
+}
+
+// βββ Memory API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+export async function searchMemory(query: string, sessionId = '') {
+ const res = await fetch(`${API_URL}/api/v1/memory/search`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ query, session_id: sessionId, limit: 10 }),
+ })
+ if (!res.ok) return { results: [] }
+ return res.json()
+}
+
+export async function getHistory(sessionId: string) {
+ const res = await fetch(`${API_URL}/api/v1/memory/history/${sessionId}`)
+ if (!res.ok) return { history: [] }
+ return res.json()
+}
diff --git a/frontend/lib/websocket.ts b/frontend/lib/websocket.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6e5bb6dfa5665452414f58cef38281a6738a75b0
--- /dev/null
+++ b/frontend/lib/websocket.ts
@@ -0,0 +1,212 @@
+// βββ WebSocket Client with Auto-Reconnect + Event Buffering ββββββββββββββββββ
+
+import { StreamEvent } from '@/types'
+
+const WS_URL = process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:7860'
+
+type EventHandler = (event: StreamEvent) => void
+
+interface WSOptions {
+ onEvent?: EventHandler
+ onConnect?: () => void
+ onDisconnect?: () => void
+ onError?: (err: Event) => void
+ maxRetries?: number
+ heartbeatInterval?: number
+}
+
+export class AgentWebSocket {
+ private ws: WebSocket | null = null
+ private url: string
+ private opts: WSOptions
+ private retryCount = 0
+ private maxRetries: number
+ private retryTimer: ReturnType | null = null
+ private heartbeatTimer: ReturnType | null = null
+ private seenIds = new Set()
+ private connected = false
+ private intentionalClose = false
+
+ constructor(path: string, opts: WSOptions = {}) {
+ this.url = `${WS_URL}${path}`
+ this.opts = opts
+ this.maxRetries = opts.maxRetries ?? 10
+ }
+
+ connect() {
+ if (this.ws?.readyState === WebSocket.OPEN) return
+ this.intentionalClose = false
+ this._connect()
+ }
+
+ private _connect() {
+ try {
+ this.ws = new WebSocket(this.url)
+
+ this.ws.onopen = () => {
+ this.connected = true
+ this.retryCount = 0
+ this.opts.onConnect?.()
+ this._startHeartbeat()
+ }
+
+ this.ws.onmessage = (e) => {
+ try {
+ const event: StreamEvent = JSON.parse(e.data)
+ // Deduplicate
+ if (event.id && this.seenIds.has(event.id)) return
+ if (event.id) this.seenIds.add(event.id)
+ if (this.seenIds.size > 500) {
+ const arr = Array.from(this.seenIds)
+ this.seenIds = new Set(arr.slice(arr.length - 300))
+ }
+ this.opts.onEvent?.(event)
+ } catch {}
+ }
+
+ this.ws.onclose = () => {
+ this.connected = false
+ this._stopHeartbeat()
+ this.opts.onDisconnect?.()
+ if (!this.intentionalClose) this._scheduleReconnect()
+ }
+
+ this.ws.onerror = (e) => {
+ this.opts.onError?.(e)
+ }
+ } catch (err) {
+ if (!this.intentionalClose) this._scheduleReconnect()
+ }
+ }
+
+ private _scheduleReconnect() {
+ if (this.retryCount >= this.maxRetries) return
+ const delay = Math.min(1000 * Math.pow(2, this.retryCount), 30000)
+ this.retryCount++
+ this.retryTimer = setTimeout(() => this._connect(), delay)
+ }
+
+ private _startHeartbeat() {
+ this.heartbeatTimer = setInterval(() => {
+ if (this.ws?.readyState === WebSocket.OPEN) {
+ this.ws.send(JSON.stringify({ type: 'ping', timestamp: Date.now() / 1000 }))
+ }
+ }, this.opts.heartbeatInterval ?? 15000)
+ }
+
+ private _stopHeartbeat() {
+ if (this.heartbeatTimer) {
+ clearInterval(this.heartbeatTimer)
+ this.heartbeatTimer = null
+ }
+ }
+
+ send(data: object) {
+ if (this.ws?.readyState === WebSocket.OPEN) {
+ this.ws.send(JSON.stringify(data))
+ }
+ }
+
+ disconnect() {
+ this.intentionalClose = true
+ if (this.retryTimer) clearTimeout(this.retryTimer)
+ this._stopHeartbeat()
+ this.ws?.close()
+ this.ws = null
+ this.connected = false
+ }
+
+ isConnected() { return this.connected }
+ getRetryCount() { return this.retryCount }
+}
+
+// βββ SSE Client for task streaming ββββββββββββββββββββββββββββββββββββββββββββ
+
+export class TaskSSEClient {
+ private url: string
+ private eventSource: EventSource | null = null
+ private onEvent: EventHandler
+
+ constructor(taskId: string, onEvent: EventHandler) {
+ this.url = `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:7860'}/api/v1/tasks/${taskId}/stream`
+ this.onEvent = onEvent
+ }
+
+ connect() {
+ this.eventSource = new EventSource(this.url)
+ this.eventSource.onmessage = (e) => {
+ try {
+ const event: StreamEvent = JSON.parse(e.data)
+ this.onEvent(event)
+ if (event.type === 'stream_end' || event.type === 'task_completed' || event.type === 'task_failed') {
+ this.disconnect()
+ }
+ } catch {}
+ }
+ this.eventSource.onerror = () => {
+ this.disconnect()
+ }
+ }
+
+ disconnect() {
+ this.eventSource?.close()
+ this.eventSource = null
+ }
+}
+
+// βββ Chat streaming via fetch (SSE) βββββββββββββββββββββββββββββββββββββββββββ
+
+export async function streamChatSSE(
+ messages: any[],
+ sessionId: string,
+ onChunk: (chunk: string) => void,
+ onDone: (full: string) => void,
+ onError?: (err: string) => void
+) {
+ const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:7860'
+ try {
+ const res = await fetch(`${API_URL}/api/v1/chat/stream`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ messages, session_id: sessionId, stream: true }),
+ })
+
+ if (!res.ok) {
+ onError?.(`HTTP ${res.status}: ${res.statusText}`)
+ return
+ }
+
+ const reader = res.body?.getReader()
+ if (!reader) return
+ const decoder = new TextDecoder()
+ let full = ''
+ let buffer = ''
+
+ while (true) {
+ const { done, value } = await reader.read()
+ if (done) break
+ buffer += decoder.decode(value, { stream: true })
+ const lines = buffer.split('\n')
+ buffer = lines.pop() ?? ''
+ for (const line of lines) {
+ if (!line.startsWith('data:')) continue
+ const raw = line.slice(5).trim()
+ if (!raw || raw === '[DONE]') continue
+ try {
+ const event = JSON.parse(raw)
+ if (event.type === 'llm_chunk') {
+ const chunk = event.data?.chunk || ''
+ full += chunk
+ onChunk(chunk)
+ } else if (event.type === 'stream_end') {
+ onDone(event.data?.full_response || full)
+ return
+ }
+ } catch {}
+ }
+ }
+ onDone(full)
+ } catch (err: any) {
+ onError?.(err.message || 'Stream error')
+ }
+}
diff --git a/frontend/next-env.d.ts b/frontend/next-env.d.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4f11a03dc6cc37f2b5105c08f2e7b24c603ab2f4
--- /dev/null
+++ b/frontend/next-env.d.ts
@@ -0,0 +1,5 @@
+///
+///
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/basic-features/typescript for more information.
diff --git a/frontend/next.config.js b/frontend/next.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..7555d1bbaf454743811666dd7b4a4c401078bb02
--- /dev/null
+++ b/frontend/next.config.js
@@ -0,0 +1,19 @@
+/** @type {import('next').NextConfig} */
+const nextConfig = {
+ output: 'standalone',
+ reactStrictMode: true,
+ env: {
+ NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:7860',
+ NEXT_PUBLIC_WS_URL: process.env.NEXT_PUBLIC_WS_URL || 'ws://localhost:7860',
+ },
+ async rewrites() {
+ return [
+ {
+ source: '/api/backend/:path*',
+ destination: `${process.env.NEXT_PUBLIC_API_URL || 'http://localhost:7860'}/api/:path*`,
+ },
+ ]
+ },
+}
+
+module.exports = nextConfig
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..5fc97bd94ebc9f7a47e0e8cdde6b4367a1203384
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,3635 @@
+{
+ "name": "devin-agent-ui",
+ "version": "2.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "devin-agent-ui",
+ "version": "2.0.0",
+ "dependencies": {
+ "clsx": "^2.1.1",
+ "date-fns": "^3.6.0",
+ "framer-motion": "^11.1.9",
+ "lucide-react": "^0.378.0",
+ "next": "14.2.3",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-markdown": "^9.0.1",
+ "react-syntax-highlighter": "^15.5.0",
+ "rehype-highlight": "^7.0.0",
+ "remark-gfm": "^4.0.0",
+ "tailwind-merge": "^2.3.0",
+ "zustand": "^4.5.2"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@types/react-syntax-highlighter": "^15.5.13",
+ "autoprefixer": "^10.0.1",
+ "postcss": "^8",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz",
+ "integrity": "sha512-W7fd7IbkfmeeY2gXrzJYDx8D2lWKbVoTIj1o1ScPHNzvp30s1AuoEFSdr39bC5sjxJaxTtq3OTCZboNp0lNWHA==",
+ "license": "MIT"
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.3.tgz",
+ "integrity": "sha512-3pEYo/RaGqPP0YzwnlmPN2puaF2WMLM3apt5jLW2fFdXD9+pqcoTzRk+iZsf8ta7+quAe4Q6Ms0nR0SFGFdS1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.3.tgz",
+ "integrity": "sha512-6adp7waE6P1TYFSXpY366xwsOnEXM+y1kgRpjSRVI2CBDOcbRjsJ67Z6EgKIqWIue52d2q/Mx8g9MszARj8IEA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.3.tgz",
+ "integrity": "sha512-cuzCE/1G0ZSnTAHJPUT1rPgQx1w5tzSX7POXSLaS7w2nIUJUD+e25QoXD/hMfxbsT9rslEXugWypJMILBj/QsA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.3.tgz",
+ "integrity": "sha512-0D4/oMM2Y9Ta3nGuCcQN8jjJjmDPYpHX9OJzqk42NZGJocU2MqhBq5tWkJrUQOQY9N+In9xOdymzapM09GeiZw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.3.tgz",
+ "integrity": "sha512-ENPiNnBNDInBLyUU5ii8PMQh+4XLr4pG51tOp6aJ9xqFQ2iRI6IH0Ds2yJkAzNV1CfyagcyzPfROMViS2wOZ9w==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.3.tgz",
+ "integrity": "sha512-BTAbq0LnCbF5MtoM7I/9UeUu/8ZBY0i8SFjUMCbPDOLv+un67e2JgyN4pmgfXBwy/I+RHu8q+k+MCkDN6P9ViQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.3.tgz",
+ "integrity": "sha512-AEHIw/dhAMLNFJFJIJIyOFDzrzI5bAjI9J26gbO5xhAKHYTZ9Or04BesFPXiAYXDNdrwTP2dQceYA4dL1geu8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.3.tgz",
+ "integrity": "sha512-vga40n1q6aYb0CLrM+eEmisfKCR45ixQYXuBXxOOmmoV8sYST9k7E3US32FsY+CkkF7NtzdcebiFT4CHuMSyZw==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.3.tgz",
+ "integrity": "sha512-Q1/zm43RWynxrO7lW4ehciQVj+5ePBhOK+/K2P7pLFX3JaJ/IZVC69SHidrmZSOkqz7ECIOhhy7XhAFG4JYyHA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@swc/counter": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
+ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
+ "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/counter": "^0.1.3",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.13",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz",
+ "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "license": "MIT"
+ },
+ "node_modules/@types/estree-jsx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz",
+ "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
+ "node_modules/@types/hast": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
+ "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/mdast": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
+ "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
+ "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.28",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz",
+ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/react-syntax-highlighter": {
+ "version": "15.5.13",
+ "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz",
+ "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@types/unist": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
+ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
+ "license": "MIT"
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
+ "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
+ "license": "ISC"
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
+ "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.2",
+ "caniuse-lite": "^1.0.30001787",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/bail": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
+ "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.29",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz",
+ "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001792",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz",
+ "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/ccount": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz",
+ "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
+ "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-html4": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz",
+ "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-entities-legacy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz",
+ "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/character-reference-invalid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz",
+ "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/comma-separated-tokens": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
+ "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/date-fns": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
+ "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decode-named-character-reference": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz",
+ "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/devlop": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz",
+ "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.354",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.354.tgz",
+ "integrity": "sha512-JaBHwWcfIdmSAfWM5l3uwjGd431j8YEMikZ+K/2nXVuBqJKyZ0f+2h4n4JY5AyNiZmnY9qQr2RU3v9DxDmHMNg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+ "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/estree-util-is-identifier-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
+ "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fault": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz",
+ "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==",
+ "license": "MIT",
+ "dependencies": {
+ "format": "^0.2.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/format": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz",
+ "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==",
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "11.18.2",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
+ "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^11.18.1",
+ "motion-utils": "^11.18.1",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hast-util-is-element": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
+ "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-parse-selector": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz",
+ "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-jsx-runtime": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
+ "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "comma-separated-tokens": "^2.0.0",
+ "devlop": "^1.0.0",
+ "estree-util-is-identifier-name": "^3.0.0",
+ "hast-util-whitespace": "^3.0.0",
+ "mdast-util-mdx-expression": "^2.0.0",
+ "mdast-util-mdx-jsx": "^3.0.0",
+ "mdast-util-mdxjs-esm": "^2.0.0",
+ "property-information": "^7.0.0",
+ "space-separated-tokens": "^2.0.0",
+ "style-to-js": "^1.0.0",
+ "unist-util-position": "^5.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-to-text": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
+ "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/unist": "^3.0.0",
+ "hast-util-is-element": "^3.0.0",
+ "unist-util-find-after": "^5.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hast-util-whitespace": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
+ "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz",
+ "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^2.0.0",
+ "comma-separated-tokens": "^1.0.0",
+ "hast-util-parse-selector": "^2.0.0",
+ "property-information": "^5.0.0",
+ "space-separated-tokens": "^1.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/hastscript/node_modules/@types/hast": {
+ "version": "2.3.10",
+ "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz",
+ "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2"
+ }
+ },
+ "node_modules/hastscript/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/hastscript/node_modules/comma-separated-tokens": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz",
+ "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/hastscript/node_modules/property-information": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz",
+ "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/hastscript/node_modules/space-separated-tokens": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz",
+ "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/highlight.js": {
+ "version": "10.7.3",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz",
+ "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/highlightjs-vue": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz",
+ "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==",
+ "license": "CC0-1.0"
+ },
+ "node_modules/html-url-attributes": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
+ "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/inline-style-parser": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
+ "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==",
+ "license": "MIT"
+ },
+ "node_modules/is-alphabetical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz",
+ "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-alphanumerical": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz",
+ "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^2.0.0",
+ "is-decimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.2",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
+ "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-decimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz",
+ "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-hexadecimal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz",
+ "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+ "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/longest-streak": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
+ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lowlight": {
+ "version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz",
+ "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==",
+ "license": "MIT",
+ "dependencies": {
+ "fault": "^1.0.0",
+ "highlight.js": "~10.7.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.378.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.378.0.tgz",
+ "integrity": "sha512-u6EPU8juLUk9ytRcyapkWI18epAv3RU+6+TC23ivjR0e+glWKBobFeSgRwOIJihzktILQuy6E0E80P2jVTDR5g==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/markdown-table": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz",
+ "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/mdast-util-find-and-replace": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
+ "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "escape-string-regexp": "^5.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-from-markdown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
+ "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark": "^4.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz",
+ "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-gfm-autolink-literal": "^2.0.0",
+ "mdast-util-gfm-footnote": "^2.0.0",
+ "mdast-util-gfm-strikethrough": "^2.0.0",
+ "mdast-util-gfm-table": "^2.0.0",
+ "mdast-util-gfm-task-list-item": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-autolink-literal": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz",
+ "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-find-and-replace": "^3.0.0",
+ "micromark-util-character": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-strikethrough": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz",
+ "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-table": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz",
+ "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "markdown-table": "^3.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-gfm-task-list-item": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz",
+ "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-expression": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz",
+ "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdx-jsx": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz",
+ "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "ccount": "^2.0.0",
+ "devlop": "^1.1.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "parse-entities": "^4.0.0",
+ "stringify-entities": "^4.0.0",
+ "unist-util-stringify-position": "^4.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-mdxjs-esm": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz",
+ "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree-jsx": "^1.0.0",
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "mdast-util-to-markdown": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-phrasing": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
+ "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-hast": {
+ "version": "13.2.1",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz",
+ "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "@ungap/structured-clone": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "trim-lines": "^3.0.0",
+ "unist-util-position": "^5.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-markdown": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz",
+ "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "@types/unist": "^3.0.0",
+ "longest-streak": "^3.0.0",
+ "mdast-util-phrasing": "^4.0.0",
+ "mdast-util-to-string": "^4.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-decode-string": "^2.0.0",
+ "unist-util-visit": "^5.0.0",
+ "zwitch": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/mdast-util-to-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz",
+ "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromark": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz",
+ "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/debug": "^4.0.0",
+ "debug": "^4.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-core-commonmark": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz",
+ "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "devlop": "^1.0.0",
+ "micromark-factory-destination": "^2.0.0",
+ "micromark-factory-label": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-factory-title": "^2.0.0",
+ "micromark-factory-whitespace": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-html-tag-name": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-subtokenize": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-extension-gfm": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz",
+ "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-extension-gfm-autolink-literal": "^2.0.0",
+ "micromark-extension-gfm-footnote": "^2.0.0",
+ "micromark-extension-gfm-strikethrough": "^2.0.0",
+ "micromark-extension-gfm-table": "^2.0.0",
+ "micromark-extension-gfm-tagfilter": "^2.0.0",
+ "micromark-extension-gfm-task-list-item": "^2.0.0",
+ "micromark-util-combine-extensions": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-autolink-literal": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz",
+ "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-footnote": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz",
+ "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-core-commonmark": "^2.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-normalize-identifier": "^2.0.0",
+ "micromark-util-sanitize-uri": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-strikethrough": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz",
+ "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-classify-character": "^2.0.0",
+ "micromark-util-resolve-all": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-table": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz",
+ "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-tagfilter": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz",
+ "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==",
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-extension-gfm-task-list-item": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz",
+ "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==",
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/micromark-factory-destination": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz",
+ "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-label": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz",
+ "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-space": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz",
+ "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-title": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz",
+ "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-factory-whitespace": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz",
+ "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-factory-space": "^2.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-character": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz",
+ "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-chunked": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz",
+ "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-classify-character": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz",
+ "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-combine-extensions": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz",
+ "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-numeric-character-reference": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz",
+ "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-decode-string": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz",
+ "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "decode-named-character-reference": "^1.0.0",
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-decode-numeric-character-reference": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-encode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz",
+ "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-html-tag-name": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz",
+ "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-normalize-identifier": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz",
+ "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-resolve-all": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz",
+ "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-sanitize-uri": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz",
+ "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "micromark-util-character": "^2.0.0",
+ "micromark-util-encode": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-subtokenize": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz",
+ "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "devlop": "^1.0.0",
+ "micromark-util-chunked": "^2.0.0",
+ "micromark-util-symbol": "^2.0.0",
+ "micromark-util-types": "^2.0.0"
+ }
+ },
+ "node_modules/micromark-util-symbol": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz",
+ "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromark-util-types": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz",
+ "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==",
+ "funding": [
+ {
+ "type": "GitHub Sponsors",
+ "url": "https://github.com/sponsors/unifiedjs"
+ },
+ {
+ "type": "OpenCollective",
+ "url": "https://opencollective.com/unified"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
+ "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^11.18.1"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "11.18.1",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz",
+ "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/next": {
+ "version": "14.2.3",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.3.tgz",
+ "integrity": "sha512-dowFkFTR8v79NPJO4QsBUtxv0g9BrS/phluVpMAt2ku7H+cbcBJlopXjkWlwxrk/xGqMemr7JkGPGemPrLLX7A==",
+ "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "14.2.3",
+ "@swc/helpers": "0.5.5",
+ "busboy": "1.6.0",
+ "caniuse-lite": "^1.0.30001579",
+ "graceful-fs": "^4.2.11",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.1"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=18.17.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "14.2.3",
+ "@next/swc-darwin-x64": "14.2.3",
+ "@next/swc-linux-arm64-gnu": "14.2.3",
+ "@next/swc-linux-arm64-musl": "14.2.3",
+ "@next/swc-linux-x64-gnu": "14.2.3",
+ "@next/swc-linux-x64-musl": "14.2.3",
+ "@next/swc-win32-arm64-msvc": "14.2.3",
+ "@next/swc-win32-ia32-msvc": "14.2.3",
+ "@next/swc-win32-x64-msvc": "14.2.3"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.41.2",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.44",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz",
+ "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/parse-entities": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
+ "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^2.0.0",
+ "character-entities-legacy": "^3.0.0",
+ "character-reference-invalid": "^2.0.0",
+ "decode-named-character-reference": "^1.0.0",
+ "is-alphanumerical": "^2.0.0",
+ "is-decimal": "^2.0.0",
+ "is-hexadecimal": "^2.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/parse-entities/node_modules/@types/unist": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz",
+ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==",
+ "license": "MIT"
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.14",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
+ "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prismjs": {
+ "version": "1.30.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
+ "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/property-information": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz",
+ "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-markdown": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz",
+ "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "devlop": "^1.0.0",
+ "hast-util-to-jsx-runtime": "^2.0.0",
+ "html-url-attributes": "^3.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-rehype": "^11.0.0",
+ "unified": "^11.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18",
+ "react": ">=18"
+ }
+ },
+ "node_modules/react-syntax-highlighter": {
+ "version": "15.6.6",
+ "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz",
+ "integrity": "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.3.1",
+ "highlight.js": "^10.4.1",
+ "highlightjs-vue": "^1.0.0",
+ "lowlight": "^1.17.0",
+ "prismjs": "^1.30.0",
+ "refractor": "^3.6.0"
+ },
+ "peerDependencies": {
+ "react": ">= 0.14.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/refractor": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
+ "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
+ "license": "MIT",
+ "dependencies": {
+ "hastscript": "^6.0.0",
+ "parse-entities": "^2.0.0",
+ "prismjs": "~1.27.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-entities": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz",
+ "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-entities-legacy": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz",
+ "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/character-reference-invalid": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz",
+ "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-alphabetical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz",
+ "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-alphanumerical": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz",
+ "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==",
+ "license": "MIT",
+ "dependencies": {
+ "is-alphabetical": "^1.0.0",
+ "is-decimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-decimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz",
+ "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/is-hexadecimal": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz",
+ "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/parse-entities": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz",
+ "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities": "^1.0.0",
+ "character-entities-legacy": "^1.0.0",
+ "character-reference-invalid": "^1.0.0",
+ "is-alphanumerical": "^1.0.0",
+ "is-decimal": "^1.0.0",
+ "is-hexadecimal": "^1.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/refractor/node_modules/prismjs": {
+ "version": "1.27.0",
+ "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz",
+ "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/rehype-highlight": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz",
+ "integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "hast-util-to-text": "^4.0.0",
+ "lowlight": "^3.0.0",
+ "unist-util-visit": "^5.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/rehype-highlight/node_modules/highlight.js": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/rehype-highlight/node_modules/lowlight": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
+ "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "devlop": "^1.0.0",
+ "highlight.js": "~11.11.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/remark-gfm": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz",
+ "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-gfm": "^3.0.0",
+ "micromark-extension-gfm": "^3.0.0",
+ "remark-parse": "^11.0.0",
+ "remark-stringify": "^11.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-parse": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
+ "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-from-markdown": "^2.0.0",
+ "micromark-util-types": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-rehype": {
+ "version": "11.1.2",
+ "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz",
+ "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/hast": "^3.0.0",
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-hast": "^13.0.0",
+ "unified": "^11.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/remark-stringify": {
+ "version": "11.0.0",
+ "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz",
+ "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/mdast": "^4.0.0",
+ "mdast-util-to-markdown": "^2.0.0",
+ "unified": "^11.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/space-separated-tokens": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz",
+ "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/stringify-entities": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz",
+ "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==",
+ "license": "MIT",
+ "dependencies": {
+ "character-entities-html4": "^2.0.0",
+ "character-entities-legacy": "^3.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/style-to-js": {
+ "version": "1.1.21",
+ "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz",
+ "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "style-to-object": "1.0.14"
+ }
+ },
+ "node_modules/style-to-object": {
+ "version": "1.0.14",
+ "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz",
+ "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==",
+ "license": "MIT",
+ "dependencies": {
+ "inline-style-parser": "0.2.7"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
+ "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz",
+ "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/trim-lines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz",
+ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/trough": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz",
+ "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unified": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz",
+ "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "bail": "^2.0.0",
+ "devlop": "^1.0.0",
+ "extend": "^3.0.0",
+ "is-plain-obj": "^4.0.0",
+ "trough": "^2.0.0",
+ "vfile": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-find-after": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
+ "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-is": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
+ "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-position": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz",
+ "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-stringify-position": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz",
+ "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz",
+ "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0",
+ "unist-util-visit-parents": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/unist-util-visit-parents": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz",
+ "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-is": "^6.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
+ "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vfile": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz",
+ "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "vfile-message": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/vfile-message": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz",
+ "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/unist": "^3.0.0",
+ "unist-util-stringify-position": "^4.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/unified"
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "4.5.7",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
+ "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "use-sync-external-store": "^1.2.2"
+ },
+ "engines": {
+ "node": ">=12.7.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=16.8",
+ "immer": ">=9.0.6",
+ "react": ">=16.8"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/zwitch": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
+ "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..5f8126b21358ae58e730b3972f60c8d47d6dac13
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "devin-agent-ui",
+ "version": "2.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev -p 3000",
+ "build": "next build",
+ "start": "next start -p 3000",
+ "lint": "next lint"
+ },
+ "dependencies": {
+ "next": "14.2.3",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-markdown": "^9.0.1",
+ "react-syntax-highlighter": "^15.5.0",
+ "remark-gfm": "^4.0.0",
+ "rehype-highlight": "^7.0.0",
+ "lucide-react": "^0.378.0",
+ "clsx": "^2.1.1",
+ "tailwind-merge": "^2.3.0",
+ "date-fns": "^3.6.0",
+ "zustand": "^4.5.2",
+ "framer-motion": "^11.1.9"
+ },
+ "devDependencies": {
+ "@types/node": "^20",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
+ "@types/react-syntax-highlighter": "^15.5.13",
+ "autoprefixer": "^10.0.1",
+ "postcss": "^8",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5"
+ }
+}
diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..33ad091d26d8a9dc95ebdf616e217d985ec215b8
--- /dev/null
+++ b/frontend/postcss.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..e9d21c792058a5f484597b3f6d6571ce7c5ae728
--- /dev/null
+++ b/frontend/tailwind.config.js
@@ -0,0 +1,61 @@
+/** @type {import('tailwindcss').Config} */
+module.exports = {
+ content: [
+ './pages/**/*.{js,ts,jsx,tsx,mdx}',
+ './components/**/*.{js,ts,jsx,tsx,mdx}',
+ './app/**/*.{js,ts,jsx,tsx,mdx}',
+ ],
+ darkMode: 'class',
+ theme: {
+ extend: {
+ colors: {
+ brand: {
+ 50: '#f0f4ff',
+ 100: '#e0e9ff',
+ 400: '#6b8cff',
+ 500: '#4f6ef7',
+ 600: '#3d5bd9',
+ 700: '#2d46b8',
+ 900: '#1a2a7a',
+ },
+ dark: {
+ 50: '#1a1b26',
+ 100: '#16171f',
+ 200: '#13141c',
+ 300: '#0f1017',
+ 400: '#0c0d12',
+ 500: '#08090d',
+ },
+ terminal: {
+ green: '#4ade80',
+ blue: '#60a5fa',
+ yellow: '#facc15',
+ red: '#f87171',
+ purple: '#c084fc',
+ cyan: '#22d3ee',
+ },
+ },
+ fontFamily: {
+ mono: ['JetBrains Mono', 'Fira Code', 'Cascadia Code', 'monospace'],
+ sans: ['Inter', 'system-ui', 'sans-serif'],
+ },
+ animation: {
+ 'fade-in': 'fadeIn 0.3s ease-in-out',
+ 'slide-up': 'slideUp 0.3s ease-out',
+ 'pulse-dot': 'pulseDot 1.5s ease-in-out infinite',
+ 'typing': 'typing 0.6s steps(3) infinite',
+ 'gradient': 'gradient 3s ease infinite',
+ 'glow': 'glow 2s ease-in-out infinite alternate',
+ },
+ keyframes: {
+ fadeIn: { '0%': { opacity: '0' }, '100%': { opacity: '1' } },
+ slideUp: { '0%': { transform: 'translateY(10px)', opacity: '0' }, '100%': { transform: 'translateY(0)', opacity: '1' } },
+ pulseDot: { '0%, 100%': { opacity: '1' }, '50%': { opacity: '0.3' } },
+ typing: { '0%': { content: '.' }, '33%': { content: '..' }, '66%': { content: '...' } },
+ gradient: { '0%, 100%': { backgroundPosition: '0% 50%' }, '50%': { backgroundPosition: '100% 50%' } },
+ glow: { '0%': { boxShadow: '0 0 5px #4f6ef7' }, '100%': { boxShadow: '0 0 20px #4f6ef7, 0 0 40px #4f6ef7' } },
+ },
+ },
+ },
+ plugins: [],
+}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 0000000000000000000000000000000000000000..109b22f5043aa526d91dcb3930912d59a67933b1
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [{ "name": "next" }],
+ "paths": { "@/*": ["./*"] }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/frontend/types/index.ts b/frontend/types/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0ed7de6171db495b8e8151b93b731263517d3050
--- /dev/null
+++ b/frontend/types/index.ts
@@ -0,0 +1,99 @@
+// βββ Core Types ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+
+export type TaskStatus =
+ | 'queued' | 'initializing' | 'planning' | 'executing'
+ | 'streaming' | 'waiting_input' | 'retrying' | 'finalizing'
+ | 'completed' | 'failed' | 'cancelled'
+
+export type EventType =
+ | 'task_created' | 'task_queued' | 'task_started' | 'plan_generated'
+ | 'step_started' | 'step_progress' | 'tool_called' | 'tool_result'
+ | 'llm_chunk' | 'memory_updated' | 'retry_attempt' | 'step_completed'
+ | 'warning' | 'error' | 'task_completed' | 'task_failed'
+ | 'heartbeat' | 'connected' | 'stream_start' | 'stream_end'
+ | 'agent_event'
+
+export interface StreamEvent {
+ id?: string
+ type: EventType | string
+ task_id?: string
+ session_id?: string
+ timestamp: number
+ data: Record
+}
+
+export interface TaskStep {
+ id: string
+ name: string
+ description: string
+ tool?: string
+ status: 'pending' | 'running' | 'completed' | 'failed'
+ output?: string
+ error?: string
+ started_at?: number
+ completed_at?: number
+ duration_ms?: number
+}
+
+export interface TaskPlan {
+ goal: string
+ steps: TaskStep[]
+ estimated_duration: number
+ tools_needed: string[]
+ created_at: number
+}
+
+export interface Task {
+ id: string
+ goal: string
+ status: TaskStatus
+ session_id: string
+ project_id: string
+ plan?: TaskPlan
+ result?: string
+ error?: string
+ metadata?: Record
+ created_at: number
+ started_at?: number
+ completed_at?: number
+ retry_count: number
+ ws_url?: string
+ stream_url?: string
+}
+
+export interface Message {
+ id: string
+ role: 'user' | 'assistant' | 'system' | 'tool'
+ content: string
+ timestamp: number
+ streaming?: boolean
+ task_id?: string
+ metadata?: Record
+}
+
+export interface TimelineEvent {
+ id: string
+ type: string
+ label: string
+ description?: string
+ timestamp: number
+ status: 'pending' | 'running' | 'completed' | 'failed' | 'warning'
+ tool?: string
+ data?: Record
+ duration_ms?: number
+}
+
+export interface AgentSession {
+ id: string
+ project_id: string
+ tasks: Task[]
+ messages: Message[]
+ timeline: TimelineEvent[]
+ active_task_id?: string
+ created_at: number
+ last_active: number
+}
+
+export type ToolIcon = {
+ [key: string]: string
+}
diff --git a/frontend/vercel.json b/frontend/vercel.json
new file mode 100644
index 0000000000000000000000000000000000000000..a7ef532ea98d3b6e8d46d8a93bb6a8ca72820777
--- /dev/null
+++ b/frontend/vercel.json
@@ -0,0 +1,28 @@
+{
+ "version": 2,
+ "framework": "nextjs",
+ "buildCommand": "npm run build",
+ "outputDirectory": ".next",
+ "installCommand": "npm install",
+ "env": {
+ "NEXT_PUBLIC_API_URL": "@next_public_api_url",
+ "NEXT_PUBLIC_WS_URL": "@next_public_ws_url",
+ "NEXT_PUBLIC_API_KEY": "@next_public_api_key"
+ },
+ "headers": [
+ {
+ "source": "/(.*)",
+ "headers": [
+ { "key": "X-Frame-Options", "value": "DENY" },
+ { "key": "X-Content-Type-Options", "value": "nosniff" },
+ { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
+ ]
+ }
+ ],
+ "rewrites": [
+ {
+ "source": "/api/backend/:path*",
+ "destination": "https://your-backend-url.com/api/:path*"
+ }
+ ]
+}