diff --git a/.gitignore b/.gitignore
index 44ab133..d14182c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,133 @@ generations/
# Log files
logs/
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
-venv/
-.env
+# ===================
+# Node.js
+# ===================
+node_modules/
+.npm
+.yarn-integrity
+.node_repl_history
+
+# ===================
+# Python
+# ===================
__pycache__/
-*.pyc
\ No newline at end of file
+*.py[cod]
+*$py.class
+*.so
+*.egg
+*.egg-info/
+dist/
+build/
+eggs/
+.eggs/
+*.manifest
+*.spec
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Virtual environments
+venv/
+.venv/
+ENV/
+env/
+.env.bak/
+
+# PyInstaller
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+coverage/
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+nosetests.xml
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# ===================
+# IDE / Editors
+# ===================
+.vscode/
+.idea/
+*.swp
+*.swo
+*.sublime-workspace
+*.sublime-project
+.spyderproject
+.spyproject
+.ropeproject
+
+# ===================
+# OS generated files
+# ===================
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+Desktop.ini
+
+# ===================
+# Build outputs
+# ===================
+ui/dist/
+ui/.vite/
+.vite/
+
+# ===================
+# Environment files
+# ===================
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
+.env.*.local
+
+# ===================
+# Cache directories
+# ===================
+.cache/
+.parcel-cache/
+.eslintcache
+.stylelintcache
+
+# ===================
+# Lock files (except package-lock.json)
+# ===================
+yarn.lock
+pnpm-lock.yaml
+poetry.lock
+Pipfile.lock
+
+# ===================
+# Misc
+# ===================
+*.bak
+*.tmp
+*.temp
+.tmp/
+.temp/
diff --git a/requirements.txt b/requirements.txt
index 8fc6ec0..a62329a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,9 @@
claude-agent-sdk>=0.1.0
python-dotenv>=1.0.0
sqlalchemy>=2.0.0
+fastapi>=0.115.0
+uvicorn[standard]>=0.32.0
+websockets>=13.0
+python-multipart>=0.0.17
+psutil>=6.0.0
+aiofiles>=24.0.0
diff --git a/server/__init__.py b/server/__init__.py
new file mode 100644
index 0000000..6db0793
--- /dev/null
+++ b/server/__init__.py
@@ -0,0 +1,8 @@
+"""
+FastAPI Backend Server
+======================
+
+Web UI server for the Autonomous Coding Agent.
+Provides REST API and WebSocket endpoints for project management,
+feature tracking, and agent control.
+"""
diff --git a/server/main.py b/server/main.py
new file mode 100644
index 0000000..00c18b7
--- /dev/null
+++ b/server/main.py
@@ -0,0 +1,171 @@
+"""
+FastAPI Main Application
+========================
+
+Main entry point for the Autonomous Coding UI server.
+Provides REST API, WebSocket, and static file serving.
+"""
+
+import shutil
+from contextlib import asynccontextmanager
+from pathlib import Path
+
+from fastapi import FastAPI, Request, WebSocket, HTTPException
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.staticfiles import StaticFiles
+from fastapi.responses import FileResponse
+
+from .routers import projects_router, features_router, agent_router
+from .websocket import project_websocket
+from .services.process_manager import cleanup_all_managers
+from .schemas import SetupStatus
+
+
+# Paths
+ROOT_DIR = Path(__file__).parent.parent
+UI_DIST_DIR = ROOT_DIR / "ui" / "dist"
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+ """Lifespan context manager for startup and shutdown."""
+ # Startup
+ yield
+ # Shutdown - cleanup all running agents
+ await cleanup_all_managers()
+
+
+# Create FastAPI app
+app = FastAPI(
+ title="Autonomous Coding UI",
+ description="Web UI for the Autonomous Coding Agent",
+ version="1.0.0",
+ lifespan=lifespan,
+)
+
+# CORS - allow only localhost origins for security
+app.add_middleware(
+ CORSMiddleware,
+ allow_origins=[
+ "http://localhost:5173", # Vite dev server
+ "http://127.0.0.1:5173",
+ "http://localhost:8000", # Production
+ "http://127.0.0.1:8000",
+ ],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+)
+
+
+# ============================================================================
+# Security Middleware
+# ============================================================================
+
+@app.middleware("http")
+async def require_localhost(request: Request, call_next):
+ """Only allow requests from localhost."""
+ client_host = request.client.host if request.client else None
+
+ # Allow localhost connections
+ if client_host not in ("127.0.0.1", "::1", "localhost", None):
+ raise HTTPException(status_code=403, detail="Localhost access only")
+
+ return await call_next(request)
+
+
+# ============================================================================
+# Include Routers
+# ============================================================================
+
+app.include_router(projects_router)
+app.include_router(features_router)
+app.include_router(agent_router)
+
+
+# ============================================================================
+# WebSocket Endpoint
+# ============================================================================
+
+@app.websocket("/ws/projects/{project_name}")
+async def websocket_endpoint(websocket: WebSocket, project_name: str):
+ """WebSocket endpoint for real-time project updates."""
+ await project_websocket(websocket, project_name)
+
+
+# ============================================================================
+# Setup & Health Endpoints
+# ============================================================================
+
+@app.get("/api/health")
+async def health_check():
+ """Health check endpoint."""
+ return {"status": "healthy"}
+
+
+@app.get("/api/setup/status", response_model=SetupStatus)
+async def setup_status():
+ """Check system setup status."""
+ # Check for Claude CLI
+ claude_cli = shutil.which("claude") is not None
+
+ # Check for credentials file
+ credentials_path = Path.home() / ".claude" / ".credentials.json"
+ credentials = credentials_path.exists()
+
+ # Check for Node.js and npm
+ node = shutil.which("node") is not None
+ npm = shutil.which("npm") is not None
+
+ return SetupStatus(
+ claude_cli=claude_cli,
+ credentials=credentials,
+ node=node,
+ npm=npm,
+ )
+
+
+# ============================================================================
+# Static File Serving (Production)
+# ============================================================================
+
+# Serve React build files if they exist
+if UI_DIST_DIR.exists():
+ # Mount static assets
+ app.mount("/assets", StaticFiles(directory=UI_DIST_DIR / "assets"), name="assets")
+
+ @app.get("/")
+ async def serve_index():
+ """Serve the React app index.html."""
+ return FileResponse(UI_DIST_DIR / "index.html")
+
+ @app.get("/{path:path}")
+ async def serve_spa(path: str):
+ """
+ Serve static files or fall back to index.html for SPA routing.
+ """
+ # Check if the path is an API route (shouldn't hit this due to router ordering)
+ if path.startswith("api/") or path.startswith("ws/"):
+ raise HTTPException(status_code=404)
+
+ # Try to serve the file directly
+ file_path = UI_DIST_DIR / path
+ if file_path.exists() and file_path.is_file():
+ return FileResponse(file_path)
+
+ # Fall back to index.html for SPA routing
+ return FileResponse(UI_DIST_DIR / "index.html")
+
+
+# ============================================================================
+# Main Entry Point
+# ============================================================================
+
+if __name__ == "__main__":
+ import uvicorn
+ uvicorn.run(
+ "server.main:app",
+ host="127.0.0.1", # Localhost only for security
+ port=8000,
+ reload=True,
+ )
diff --git a/server/routers/__init__.py b/server/routers/__init__.py
new file mode 100644
index 0000000..c8411c1
--- /dev/null
+++ b/server/routers/__init__.py
@@ -0,0 +1,12 @@
+"""
+API Routers
+===========
+
+FastAPI routers for different API endpoints.
+"""
+
+from .projects import router as projects_router
+from .features import router as features_router
+from .agent import router as agent_router
+
+__all__ = ["projects_router", "features_router", "agent_router"]
diff --git a/server/routers/agent.py b/server/routers/agent.py
new file mode 100644
index 0000000..1751404
--- /dev/null
+++ b/server/routers/agent.py
@@ -0,0 +1,128 @@
+"""
+Agent Router
+============
+
+API endpoints for agent control (start/stop/pause/resume).
+"""
+
+import re
+from pathlib import Path
+
+from fastapi import APIRouter, HTTPException
+
+from ..schemas import AgentStatus, AgentActionResponse
+from ..services.process_manager import get_manager
+
+# Lazy import to avoid sys.path manipulation at module level
+_GENERATIONS_DIR = None
+
+
+def _get_generations_dir():
+ """Lazy import of GENERATIONS_DIR."""
+ global _GENERATIONS_DIR
+ if _GENERATIONS_DIR is None:
+ import sys
+ root = Path(__file__).parent.parent.parent
+ if str(root) not in sys.path:
+ sys.path.insert(0, str(root))
+ from start import GENERATIONS_DIR
+ _GENERATIONS_DIR = GENERATIONS_DIR
+ return _GENERATIONS_DIR
+
+
+router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"])
+
+# Root directory for process manager
+ROOT_DIR = Path(__file__).parent.parent.parent
+
+
+def validate_project_name(name: str) -> str:
+ """Validate and sanitize project name to prevent path traversal."""
+ if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid project name"
+ )
+ return name
+
+
+def get_project_manager(project_name: str):
+ """Get the process manager for a project."""
+ project_name = validate_project_name(project_name)
+ project_dir = _get_generations_dir() / project_name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found")
+
+ return get_manager(project_name, ROOT_DIR)
+
+
+@router.get("/status", response_model=AgentStatus)
+async def get_agent_status(project_name: str):
+ """Get the current status of the agent for a project."""
+ manager = get_project_manager(project_name)
+
+ # Run healthcheck to detect crashed processes
+ await manager.healthcheck()
+
+ return AgentStatus(
+ status=manager.status,
+ pid=manager.pid,
+ started_at=manager.started_at,
+ )
+
+
+@router.post("/start", response_model=AgentActionResponse)
+async def start_agent(project_name: str):
+ """Start the agent for a project."""
+ manager = get_project_manager(project_name)
+
+ success, message = await manager.start()
+
+ return AgentActionResponse(
+ success=success,
+ status=manager.status,
+ message=message,
+ )
+
+
+@router.post("/stop", response_model=AgentActionResponse)
+async def stop_agent(project_name: str):
+ """Stop the agent for a project."""
+ manager = get_project_manager(project_name)
+
+ success, message = await manager.stop()
+
+ return AgentActionResponse(
+ success=success,
+ status=manager.status,
+ message=message,
+ )
+
+
+@router.post("/pause", response_model=AgentActionResponse)
+async def pause_agent(project_name: str):
+ """Pause the agent for a project."""
+ manager = get_project_manager(project_name)
+
+ success, message = await manager.pause()
+
+ return AgentActionResponse(
+ success=success,
+ status=manager.status,
+ message=message,
+ )
+
+
+@router.post("/resume", response_model=AgentActionResponse)
+async def resume_agent(project_name: str):
+ """Resume a paused agent."""
+ manager = get_project_manager(project_name)
+
+ success, message = await manager.resume()
+
+ return AgentActionResponse(
+ success=success,
+ status=manager.status,
+ message=message,
+ )
diff --git a/server/routers/features.py b/server/routers/features.py
new file mode 100644
index 0000000..56e4d6d
--- /dev/null
+++ b/server/routers/features.py
@@ -0,0 +1,282 @@
+"""
+Features Router
+===============
+
+API endpoints for feature/test case management.
+"""
+
+import re
+import logging
+from pathlib import Path
+from contextlib import contextmanager
+
+from fastapi import APIRouter, HTTPException
+
+from ..schemas import (
+ FeatureCreate,
+ FeatureResponse,
+ FeatureListResponse,
+)
+
+# Lazy imports to avoid circular dependencies
+_GENERATIONS_DIR = None
+_create_database = None
+_Feature = None
+
+logger = logging.getLogger(__name__)
+
+
+def _get_generations_dir():
+ """Lazy import of GENERATIONS_DIR."""
+ global _GENERATIONS_DIR
+ if _GENERATIONS_DIR is None:
+ import sys
+ from pathlib import Path
+ root = Path(__file__).parent.parent.parent
+ if str(root) not in sys.path:
+ sys.path.insert(0, str(root))
+ from start import GENERATIONS_DIR
+ _GENERATIONS_DIR = GENERATIONS_DIR
+ return _GENERATIONS_DIR
+
+
+def _get_db_classes():
+ """Lazy import of database classes."""
+ global _create_database, _Feature
+ if _create_database is None:
+ import sys
+ from pathlib import Path
+ root = Path(__file__).parent.parent.parent
+ if str(root) not in sys.path:
+ sys.path.insert(0, str(root))
+ from api.database import create_database, Feature
+ _create_database = create_database
+ _Feature = Feature
+ return _create_database, _Feature
+
+
+router = APIRouter(prefix="/api/projects/{project_name}/features", tags=["features"])
+
+
+def validate_project_name(name: str) -> str:
+ """Validate and sanitize project name to prevent path traversal."""
+ if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid project name"
+ )
+ return name
+
+
+@contextmanager
+def get_db_session(project_dir: Path):
+ """
+ Context manager for database sessions.
+ Ensures session is always closed, even on exceptions.
+ """
+ create_database, _ = _get_db_classes()
+ _, SessionLocal = create_database(project_dir)
+ session = SessionLocal()
+ try:
+ yield session
+ finally:
+ session.close()
+
+
+def feature_to_response(f) -> FeatureResponse:
+ """Convert a Feature model to a FeatureResponse."""
+ return FeatureResponse(
+ id=f.id,
+ priority=f.priority,
+ category=f.category,
+ name=f.name,
+ description=f.description,
+ steps=f.steps if isinstance(f.steps, list) else [],
+ passes=f.passes,
+ )
+
+
+@router.get("", response_model=FeatureListResponse)
+async def list_features(project_name: str):
+ """
+ List all features for a project organized by status.
+
+ Returns features in three lists:
+ - pending: passes=False, not currently being worked on
+ - in_progress: features currently being worked on (tracked via agent output)
+ - done: passes=True
+ """
+ project_name = validate_project_name(project_name)
+ project_dir = _get_generations_dir() / project_name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found")
+
+ db_file = project_dir / "features.db"
+ if not db_file.exists():
+ return FeatureListResponse(pending=[], in_progress=[], done=[])
+
+ _, Feature = _get_db_classes()
+
+ try:
+ with get_db_session(project_dir) as session:
+ all_features = session.query(Feature).order_by(Feature.priority).all()
+
+ pending = []
+ done = []
+
+ for f in all_features:
+ feature_response = feature_to_response(f)
+ if f.passes:
+ done.append(feature_response)
+ else:
+ pending.append(feature_response)
+
+ return FeatureListResponse(
+ pending=pending,
+ in_progress=[],
+ done=done,
+ )
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.exception("Database error in list_features")
+ raise HTTPException(status_code=500, detail="Database error occurred")
+
+
+@router.post("", response_model=FeatureResponse)
+async def create_feature(project_name: str, feature: FeatureCreate):
+ """Create a new feature/test case manually."""
+ project_name = validate_project_name(project_name)
+ project_dir = _get_generations_dir() / project_name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found")
+
+ _, Feature = _get_db_classes()
+
+ try:
+ with get_db_session(project_dir) as session:
+ # Get next priority if not specified
+ if feature.priority is None:
+ max_priority = session.query(Feature).order_by(Feature.priority.desc()).first()
+ priority = (max_priority.priority + 1) if max_priority else 1
+ else:
+ priority = feature.priority
+
+ # Create new feature
+ db_feature = Feature(
+ priority=priority,
+ category=feature.category,
+ name=feature.name,
+ description=feature.description,
+ steps=feature.steps,
+ passes=False,
+ )
+
+ session.add(db_feature)
+ session.commit()
+ session.refresh(db_feature)
+
+ return feature_to_response(db_feature)
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.exception("Failed to create feature")
+ raise HTTPException(status_code=500, detail="Failed to create feature")
+
+
+@router.get("/{feature_id}", response_model=FeatureResponse)
+async def get_feature(project_name: str, feature_id: int):
+ """Get details of a specific feature."""
+ project_name = validate_project_name(project_name)
+ project_dir = _get_generations_dir() / project_name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found")
+
+ db_file = project_dir / "features.db"
+ if not db_file.exists():
+ raise HTTPException(status_code=404, detail="No features database found")
+
+ _, Feature = _get_db_classes()
+
+ try:
+ with get_db_session(project_dir) as session:
+ feature = session.query(Feature).filter(Feature.id == feature_id).first()
+
+ if not feature:
+ raise HTTPException(status_code=404, detail=f"Feature {feature_id} not found")
+
+ return feature_to_response(feature)
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.exception("Database error in get_feature")
+ raise HTTPException(status_code=500, detail="Database error occurred")
+
+
+@router.delete("/{feature_id}")
+async def delete_feature(project_name: str, feature_id: int):
+ """Delete a feature."""
+ project_name = validate_project_name(project_name)
+ project_dir = _get_generations_dir() / project_name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found")
+
+ _, Feature = _get_db_classes()
+
+ try:
+ with get_db_session(project_dir) as session:
+ feature = session.query(Feature).filter(Feature.id == feature_id).first()
+
+ if not feature:
+ raise HTTPException(status_code=404, detail=f"Feature {feature_id} not found")
+
+ session.delete(feature)
+ session.commit()
+
+ return {"success": True, "message": f"Feature {feature_id} deleted"}
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.exception("Failed to delete feature")
+ raise HTTPException(status_code=500, detail="Failed to delete feature")
+
+
+@router.patch("/{feature_id}/skip")
+async def skip_feature(project_name: str, feature_id: int):
+ """
+ Mark a feature as skipped by moving it to the end of the priority queue.
+
+ This doesn't delete the feature but gives it a very high priority number
+ so it will be processed last.
+ """
+ project_name = validate_project_name(project_name)
+ project_dir = _get_generations_dir() / project_name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found")
+
+ _, Feature = _get_db_classes()
+
+ try:
+ with get_db_session(project_dir) as session:
+ feature = session.query(Feature).filter(Feature.id == feature_id).first()
+
+ if not feature:
+ raise HTTPException(status_code=404, detail=f"Feature {feature_id} not found")
+
+ # Set priority to max + 1000 to push to end
+ max_priority = session.query(Feature).order_by(Feature.priority.desc()).first()
+ feature.priority = (max_priority.priority if max_priority else 0) + 1000
+
+ session.commit()
+
+ return {"success": True, "message": f"Feature {feature_id} moved to end of queue"}
+ except HTTPException:
+ raise
+ except Exception as e:
+ logger.exception("Failed to skip feature")
+ raise HTTPException(status_code=500, detail="Failed to skip feature")
diff --git a/server/routers/projects.py b/server/routers/projects.py
new file mode 100644
index 0000000..13fc827
--- /dev/null
+++ b/server/routers/projects.py
@@ -0,0 +1,239 @@
+"""
+Projects Router
+===============
+
+API endpoints for project management.
+"""
+
+import re
+import shutil
+from pathlib import Path
+
+from fastapi import APIRouter, HTTPException
+
+from ..schemas import (
+ ProjectCreate,
+ ProjectSummary,
+ ProjectDetail,
+ ProjectPrompts,
+ ProjectPromptsUpdate,
+ ProjectStats,
+)
+
+# Lazy imports to avoid sys.path manipulation at module level
+_imports_initialized = False
+_GENERATIONS_DIR = None
+_get_existing_projects = None
+_check_spec_exists = None
+_scaffold_project_prompts = None
+_get_project_prompts_dir = None
+_count_passing_tests = None
+
+
+def _init_imports():
+ """Lazy import of project-level modules."""
+ global _imports_initialized, _GENERATIONS_DIR, _get_existing_projects
+ global _check_spec_exists, _scaffold_project_prompts, _get_project_prompts_dir
+ global _count_passing_tests
+
+ if _imports_initialized:
+ return
+
+ import sys
+ root = Path(__file__).parent.parent.parent
+ if str(root) not in sys.path:
+ sys.path.insert(0, str(root))
+
+ from start import GENERATIONS_DIR, get_existing_projects, check_spec_exists
+ from prompts import scaffold_project_prompts, get_project_prompts_dir
+ from progress import count_passing_tests
+
+ _GENERATIONS_DIR = GENERATIONS_DIR
+ _get_existing_projects = get_existing_projects
+ _check_spec_exists = check_spec_exists
+ _scaffold_project_prompts = scaffold_project_prompts
+ _get_project_prompts_dir = get_project_prompts_dir
+ _count_passing_tests = count_passing_tests
+ _imports_initialized = True
+
+
+router = APIRouter(prefix="/api/projects", tags=["projects"])
+
+
+def validate_project_name(name: str) -> str:
+ """Validate and sanitize project name to prevent path traversal."""
+ if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
+ raise HTTPException(
+ status_code=400,
+ detail="Invalid project name. Use only letters, numbers, hyphens, and underscores (1-50 chars)."
+ )
+ return name
+
+
+def get_project_stats(project_dir: Path) -> ProjectStats:
+ """Get statistics for a project."""
+ _init_imports()
+ passing, total = _count_passing_tests(project_dir)
+ percentage = (passing / total * 100) if total > 0 else 0.0
+ return ProjectStats(passing=passing, total=total, percentage=round(percentage, 1))
+
+
+@router.get("", response_model=list[ProjectSummary])
+async def list_projects():
+ """List all projects in the generations directory."""
+ _init_imports()
+ projects = _get_existing_projects()
+ result = []
+
+ for name in projects:
+ project_dir = _GENERATIONS_DIR / name
+ has_spec = _check_spec_exists(project_dir)
+ stats = get_project_stats(project_dir)
+
+ result.append(ProjectSummary(
+ name=name,
+ has_spec=has_spec,
+ stats=stats,
+ ))
+
+ return result
+
+
+@router.post("", response_model=ProjectSummary)
+async def create_project(project: ProjectCreate):
+ """Create a new project with scaffolded prompts."""
+ _init_imports()
+ name = validate_project_name(project.name)
+
+ project_dir = _GENERATIONS_DIR / name
+
+ if project_dir.exists():
+ raise HTTPException(
+ status_code=409,
+ detail=f"Project '{name}' already exists"
+ )
+
+ # Create project directory
+ project_dir.mkdir(parents=True, exist_ok=True)
+
+ # Scaffold prompts
+ _scaffold_project_prompts(project_dir)
+
+ return ProjectSummary(
+ name=name,
+ has_spec=False, # Just created, no spec yet
+ stats=ProjectStats(passing=0, total=0, percentage=0.0),
+ )
+
+
+@router.get("/{name}", response_model=ProjectDetail)
+async def get_project(name: str):
+ """Get detailed information about a project."""
+ _init_imports()
+ name = validate_project_name(name)
+ project_dir = _GENERATIONS_DIR / name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
+
+ has_spec = _check_spec_exists(project_dir)
+ stats = get_project_stats(project_dir)
+ prompts_dir = _get_project_prompts_dir(project_dir)
+
+ return ProjectDetail(
+ name=name,
+ has_spec=has_spec,
+ stats=stats,
+ prompts_dir=str(prompts_dir),
+ )
+
+
+@router.delete("/{name}")
+async def delete_project(name: str):
+ """Delete a project and all its files."""
+ _init_imports()
+ name = validate_project_name(name)
+ project_dir = _GENERATIONS_DIR / name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
+
+ # Check if agent is running
+ lock_file = project_dir / ".agent.lock"
+ if lock_file.exists():
+ raise HTTPException(
+ status_code=409,
+ detail="Cannot delete project while agent is running. Stop the agent first."
+ )
+
+ try:
+ shutil.rmtree(project_dir)
+ return {"success": True, "message": f"Project '{name}' deleted"}
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=f"Failed to delete project: {e}")
+
+
+@router.get("/{name}/prompts", response_model=ProjectPrompts)
+async def get_project_prompts(name: str):
+ """Get the content of project prompt files."""
+ _init_imports()
+ name = validate_project_name(name)
+ project_dir = _GENERATIONS_DIR / name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
+
+ prompts_dir = _get_project_prompts_dir(project_dir)
+
+ def read_file(filename: str) -> str:
+ filepath = prompts_dir / filename
+ if filepath.exists():
+ try:
+ return filepath.read_text(encoding="utf-8")
+ except Exception:
+ return ""
+ return ""
+
+ return ProjectPrompts(
+ app_spec=read_file("app_spec.txt"),
+ initializer_prompt=read_file("initializer_prompt.md"),
+ coding_prompt=read_file("coding_prompt.md"),
+ )
+
+
+@router.put("/{name}/prompts")
+async def update_project_prompts(name: str, prompts: ProjectPromptsUpdate):
+ """Update project prompt files."""
+ _init_imports()
+ name = validate_project_name(name)
+ project_dir = _GENERATIONS_DIR / name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
+
+ prompts_dir = _get_project_prompts_dir(project_dir)
+ prompts_dir.mkdir(parents=True, exist_ok=True)
+
+ def write_file(filename: str, content: str | None):
+ if content is not None:
+ filepath = prompts_dir / filename
+ filepath.write_text(content, encoding="utf-8")
+
+ write_file("app_spec.txt", prompts.app_spec)
+ write_file("initializer_prompt.md", prompts.initializer_prompt)
+ write_file("coding_prompt.md", prompts.coding_prompt)
+
+ return {"success": True, "message": "Prompts updated"}
+
+
+@router.get("/{name}/stats", response_model=ProjectStats)
+async def get_project_stats_endpoint(name: str):
+ """Get current progress statistics for a project."""
+ _init_imports()
+ name = validate_project_name(name)
+ project_dir = _GENERATIONS_DIR / name
+
+ if not project_dir.exists():
+ raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
+
+ return get_project_stats(project_dir)
diff --git a/server/schemas.py b/server/schemas.py
new file mode 100644
index 0000000..fee89c7
--- /dev/null
+++ b/server/schemas.py
@@ -0,0 +1,152 @@
+"""
+Pydantic Schemas
+================
+
+Request/Response models for the API endpoints.
+"""
+
+from datetime import datetime
+from typing import Literal
+from pydantic import BaseModel, Field
+
+
+# ============================================================================
+# Project Schemas
+# ============================================================================
+
+class ProjectCreate(BaseModel):
+ """Request schema for creating a new project."""
+ name: str = Field(..., min_length=1, max_length=50, pattern=r'^[a-zA-Z0-9_-]+$')
+ spec_method: Literal["claude", "manual"] = "claude"
+
+
+class ProjectStats(BaseModel):
+ """Project statistics."""
+ passing: int = 0
+ total: int = 0
+ percentage: float = 0.0
+
+
+class ProjectSummary(BaseModel):
+ """Summary of a project for list view."""
+ name: str
+ has_spec: bool
+ stats: ProjectStats
+
+
+class ProjectDetail(BaseModel):
+ """Detailed project information."""
+ name: str
+ has_spec: bool
+ stats: ProjectStats
+ prompts_dir: str
+
+
+class ProjectPrompts(BaseModel):
+ """Project prompt files content."""
+ app_spec: str = ""
+ initializer_prompt: str = ""
+ coding_prompt: str = ""
+
+
+class ProjectPromptsUpdate(BaseModel):
+ """Request schema for updating project prompts."""
+ app_spec: str | None = None
+ initializer_prompt: str | None = None
+ coding_prompt: str | None = None
+
+
+# ============================================================================
+# Feature Schemas
+# ============================================================================
+
+class FeatureBase(BaseModel):
+ """Base feature attributes."""
+ category: str
+ name: str
+ description: str
+ steps: list[str]
+
+
+class FeatureCreate(FeatureBase):
+ """Request schema for creating a new feature."""
+ priority: int | None = None
+
+
+class FeatureResponse(FeatureBase):
+ """Response schema for a feature."""
+ id: int
+ priority: int
+ passes: bool
+
+ class Config:
+ from_attributes = True
+
+
+class FeatureListResponse(BaseModel):
+ """Response containing list of features organized by status."""
+ pending: list[FeatureResponse]
+ in_progress: list[FeatureResponse]
+ done: list[FeatureResponse]
+
+
+# ============================================================================
+# Agent Schemas
+# ============================================================================
+
+class AgentStatus(BaseModel):
+ """Current agent status."""
+ status: Literal["stopped", "running", "paused", "crashed"]
+ pid: int | None = None
+ started_at: datetime | None = None
+
+
+class AgentActionResponse(BaseModel):
+ """Response for agent control actions."""
+ success: bool
+ status: str
+ message: str = ""
+
+
+# ============================================================================
+# Setup Schemas
+# ============================================================================
+
+class SetupStatus(BaseModel):
+ """System setup status."""
+ claude_cli: bool
+ credentials: bool
+ node: bool
+ npm: bool
+
+
+# ============================================================================
+# WebSocket Message Schemas
+# ============================================================================
+
+class WSProgressMessage(BaseModel):
+ """WebSocket message for progress updates."""
+ type: Literal["progress"] = "progress"
+ passing: int
+ total: int
+ percentage: float
+
+
+class WSFeatureUpdateMessage(BaseModel):
+ """WebSocket message for feature status updates."""
+ type: Literal["feature_update"] = "feature_update"
+ feature_id: int
+ passes: bool
+
+
+class WSLogMessage(BaseModel):
+ """WebSocket message for agent log output."""
+ type: Literal["log"] = "log"
+ line: str
+ timestamp: datetime
+
+
+class WSAgentStatusMessage(BaseModel):
+ """WebSocket message for agent status changes."""
+ type: Literal["agent_status"] = "agent_status"
+ status: str
diff --git a/server/services/__init__.py b/server/services/__init__.py
new file mode 100644
index 0000000..eb5c35c
--- /dev/null
+++ b/server/services/__init__.py
@@ -0,0 +1,10 @@
+"""
+Backend Services
+================
+
+Business logic and process management services.
+"""
+
+from .process_manager import AgentProcessManager
+
+__all__ = ["AgentProcessManager"]
diff --git a/server/services/process_manager.py b/server/services/process_manager.py
new file mode 100644
index 0000000..4b13555
--- /dev/null
+++ b/server/services/process_manager.py
@@ -0,0 +1,403 @@
+"""
+Agent Process Manager
+=====================
+
+Manages the lifecycle of agent subprocesses per project.
+Provides start/stop/pause/resume functionality with cross-platform support.
+"""
+
+import asyncio
+import logging
+import re
+import subprocess
+import sys
+import threading
+from datetime import datetime
+from pathlib import Path
+from typing import Literal, Callable, Awaitable, Set
+
+import psutil
+
+
+logger = logging.getLogger(__name__)
+
+# Patterns for sensitive data that should be redacted from output
+SENSITIVE_PATTERNS = [
+ r'sk-[a-zA-Z0-9]{20,}', # Anthropic API keys
+ r'ANTHROPIC_API_KEY=[^\s]+',
+ r'api[_-]?key[=:][^\s]+',
+ r'token[=:][^\s]+',
+ r'password[=:][^\s]+',
+ r'secret[=:][^\s]+',
+ r'ghp_[a-zA-Z0-9]{36,}', # GitHub personal access tokens
+ r'gho_[a-zA-Z0-9]{36,}', # GitHub OAuth tokens
+ r'ghs_[a-zA-Z0-9]{36,}', # GitHub server tokens
+ r'ghr_[a-zA-Z0-9]{36,}', # GitHub refresh tokens
+ r'aws[_-]?access[_-]?key[=:][^\s]+', # AWS keys
+ r'aws[_-]?secret[=:][^\s]+',
+]
+
+
+def sanitize_output(line: str) -> str:
+ """Remove sensitive information from output lines."""
+ for pattern in SENSITIVE_PATTERNS:
+ line = re.sub(pattern, '[REDACTED]', line, flags=re.IGNORECASE)
+ return line
+
+
+class AgentProcessManager:
+ """
+ Manages agent subprocess lifecycle for a single project.
+
+ Provides start/stop/pause/resume with cross-platform support via psutil.
+ Supports multiple output callbacks for WebSocket clients.
+ """
+
+ def __init__(
+ self,
+ project_name: str,
+ root_dir: Path,
+ ):
+ """
+ Initialize the process manager.
+
+ Args:
+ project_name: Name of the project
+ root_dir: Root directory of the autonomous-coding-ui project
+ """
+ self.project_name = project_name
+ self.root_dir = root_dir
+ self.process: subprocess.Popen | None = None
+ self._status: Literal["stopped", "running", "paused", "crashed"] = "stopped"
+ self.started_at: datetime | None = None
+ self._output_task: asyncio.Task | None = None
+
+ # Support multiple callbacks (for multiple WebSocket clients)
+ self._output_callbacks: Set[Callable[[str], Awaitable[None]]] = set()
+ self._status_callbacks: Set[Callable[[str], Awaitable[None]]] = set()
+ self._callbacks_lock = threading.Lock()
+
+ # Lock file to prevent multiple instances
+ self.lock_file = self.root_dir / "generations" / project_name / ".agent.lock"
+
+ @property
+ def status(self) -> Literal["stopped", "running", "paused", "crashed"]:
+ return self._status
+
+ @status.setter
+ def status(self, value: Literal["stopped", "running", "paused", "crashed"]):
+ old_status = self._status
+ self._status = value
+ if old_status != value:
+ self._notify_status_change(value)
+
+ def _notify_status_change(self, status: str) -> None:
+ """Notify all registered callbacks of status change."""
+ with self._callbacks_lock:
+ callbacks = list(self._status_callbacks)
+
+ for callback in callbacks:
+ try:
+ # Schedule the callback in the event loop
+ loop = asyncio.get_running_loop()
+ loop.create_task(self._safe_callback(callback, status))
+ except RuntimeError:
+ # No running event loop
+ pass
+
+ async def _safe_callback(self, callback: Callable, *args) -> None:
+ """Safely execute a callback, catching and logging any errors."""
+ try:
+ await callback(*args)
+ except Exception as e:
+ logger.warning(f"Callback error: {e}")
+
+ def add_output_callback(self, callback: Callable[[str], Awaitable[None]]) -> None:
+ """Add a callback for output lines."""
+ with self._callbacks_lock:
+ self._output_callbacks.add(callback)
+
+ def remove_output_callback(self, callback: Callable[[str], Awaitable[None]]) -> None:
+ """Remove an output callback."""
+ with self._callbacks_lock:
+ self._output_callbacks.discard(callback)
+
+ def add_status_callback(self, callback: Callable[[str], Awaitable[None]]) -> None:
+ """Add a callback for status changes."""
+ with self._callbacks_lock:
+ self._status_callbacks.add(callback)
+
+ def remove_status_callback(self, callback: Callable[[str], Awaitable[None]]) -> None:
+ """Remove a status callback."""
+ with self._callbacks_lock:
+ self._status_callbacks.discard(callback)
+
+ @property
+ def pid(self) -> int | None:
+ return self.process.pid if self.process else None
+
+ def _check_lock(self) -> bool:
+ """Check if another agent is already running for this project."""
+ if not self.lock_file.exists():
+ return True
+
+ try:
+ pid = int(self.lock_file.read_text().strip())
+ if psutil.pid_exists(pid):
+ # Check if it's actually our agent process
+ try:
+ proc = psutil.Process(pid)
+ cmdline = " ".join(proc.cmdline())
+ if "autonomous_agent_demo.py" in cmdline:
+ return False # Another agent is running
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
+ pass
+ # Stale lock file
+ self.lock_file.unlink(missing_ok=True)
+ return True
+ except (ValueError, OSError):
+ self.lock_file.unlink(missing_ok=True)
+ return True
+
+ def _create_lock(self) -> None:
+ """Create lock file with current process PID."""
+ self.lock_file.parent.mkdir(parents=True, exist_ok=True)
+ if self.process:
+ self.lock_file.write_text(str(self.process.pid))
+
+ def _remove_lock(self) -> None:
+ """Remove lock file."""
+ self.lock_file.unlink(missing_ok=True)
+
+ async def _broadcast_output(self, line: str) -> None:
+ """Broadcast output line to all registered callbacks."""
+ with self._callbacks_lock:
+ callbacks = list(self._output_callbacks)
+
+ for callback in callbacks:
+ await self._safe_callback(callback, line)
+
+ async def _stream_output(self) -> None:
+ """Stream process output to callbacks."""
+ if not self.process or not self.process.stdout:
+ return
+
+ try:
+ loop = asyncio.get_running_loop()
+ while True:
+ # Use run_in_executor for blocking readline
+ line = await loop.run_in_executor(
+ None, self.process.stdout.readline
+ )
+ if not line:
+ break
+
+ decoded = line.decode("utf-8", errors="replace").rstrip()
+ sanitized = sanitize_output(decoded)
+
+ await self._broadcast_output(sanitized)
+
+ except asyncio.CancelledError:
+ raise
+ except Exception as e:
+ logger.warning(f"Output streaming error: {e}")
+ finally:
+ # Check if process ended
+ if self.process and self.process.poll() is not None:
+ exit_code = self.process.returncode
+ if exit_code != 0 and self.status == "running":
+ self.status = "crashed"
+ elif self.status == "running":
+ self.status = "stopped"
+ self._remove_lock()
+
+ async def start(self) -> tuple[bool, str]:
+ """
+ Start the agent as a subprocess.
+
+ Returns:
+ Tuple of (success, message)
+ """
+ if self.status in ("running", "paused"):
+ return False, f"Agent is already {self.status}"
+
+ if not self._check_lock():
+ return False, "Another agent instance is already running for this project"
+
+ # Build command
+ cmd = [
+ sys.executable,
+ str(self.root_dir / "autonomous_agent_demo.py"),
+ "--project-dir",
+ self.project_name,
+ ]
+
+ try:
+ # Start subprocess with piped stdout/stderr
+ self.process = subprocess.Popen(
+ cmd,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ cwd=str(self.root_dir),
+ )
+
+ self._create_lock()
+ self.started_at = datetime.now()
+ self.status = "running"
+
+ # Start output streaming task
+ self._output_task = asyncio.create_task(self._stream_output())
+
+ return True, f"Agent started with PID {self.process.pid}"
+ except Exception as e:
+ logger.exception("Failed to start agent")
+ return False, f"Failed to start agent: {e}"
+
+ async def stop(self) -> tuple[bool, str]:
+ """
+ Stop the agent (SIGTERM then SIGKILL if needed).
+
+ Returns:
+ Tuple of (success, message)
+ """
+ if not self.process or self.status == "stopped":
+ return False, "Agent is not running"
+
+ try:
+ # Cancel output streaming
+ if self._output_task:
+ self._output_task.cancel()
+ try:
+ await self._output_task
+ except asyncio.CancelledError:
+ pass
+
+ # Terminate gracefully first
+ self.process.terminate()
+
+ # Wait up to 5 seconds for graceful shutdown
+ loop = asyncio.get_running_loop()
+ try:
+ await asyncio.wait_for(
+ loop.run_in_executor(None, self.process.wait),
+ timeout=5.0
+ )
+ except asyncio.TimeoutError:
+ # Force kill if still running
+ self.process.kill()
+ await loop.run_in_executor(None, self.process.wait)
+
+ self._remove_lock()
+ self.status = "stopped"
+ self.process = None
+ self.started_at = None
+
+ return True, "Agent stopped"
+ except Exception as e:
+ logger.exception("Failed to stop agent")
+ return False, f"Failed to stop agent: {e}"
+
+ async def pause(self) -> tuple[bool, str]:
+ """
+ Pause the agent using psutil for cross-platform support.
+
+ Returns:
+ Tuple of (success, message)
+ """
+ if not self.process or self.status != "running":
+ return False, "Agent is not running"
+
+ try:
+ proc = psutil.Process(self.process.pid)
+ proc.suspend()
+ self.status = "paused"
+ return True, "Agent paused"
+ except psutil.NoSuchProcess:
+ self.status = "crashed"
+ self._remove_lock()
+ return False, "Agent process no longer exists"
+ except Exception as e:
+ logger.exception("Failed to pause agent")
+ return False, f"Failed to pause agent: {e}"
+
+ async def resume(self) -> tuple[bool, str]:
+ """
+ Resume a paused agent.
+
+ Returns:
+ Tuple of (success, message)
+ """
+ if not self.process or self.status != "paused":
+ return False, "Agent is not paused"
+
+ try:
+ proc = psutil.Process(self.process.pid)
+ proc.resume()
+ self.status = "running"
+ return True, "Agent resumed"
+ except psutil.NoSuchProcess:
+ self.status = "crashed"
+ self._remove_lock()
+ return False, "Agent process no longer exists"
+ except Exception as e:
+ logger.exception("Failed to resume agent")
+ return False, f"Failed to resume agent: {e}"
+
+ async def healthcheck(self) -> bool:
+ """
+ Check if the agent process is still alive.
+
+ Updates status to 'crashed' if process has died unexpectedly.
+
+ Returns:
+ True if healthy, False otherwise
+ """
+ if not self.process:
+ return self.status == "stopped"
+
+ poll = self.process.poll()
+ if poll is not None:
+ # Process has terminated
+ if self.status in ("running", "paused"):
+ self.status = "crashed"
+ self._remove_lock()
+ return False
+
+ return True
+
+ def get_status_dict(self) -> dict:
+ """Get current status as a dictionary."""
+ return {
+ "status": self.status,
+ "pid": self.pid,
+ "started_at": self.started_at.isoformat() if self.started_at else None,
+ }
+
+
+# Global registry of process managers per project with thread safety
+_managers: dict[str, AgentProcessManager] = {}
+_managers_lock = threading.Lock()
+
+
+def get_manager(project_name: str, root_dir: Path) -> AgentProcessManager:
+ """Get or create a process manager for a project (thread-safe)."""
+ with _managers_lock:
+ if project_name not in _managers:
+ _managers[project_name] = AgentProcessManager(project_name, root_dir)
+ return _managers[project_name]
+
+
+async def cleanup_all_managers() -> None:
+ """Stop all running agents. Called on server shutdown."""
+ with _managers_lock:
+ managers = list(_managers.values())
+
+ for manager in managers:
+ try:
+ if manager.status != "stopped":
+ await manager.stop()
+ except Exception as e:
+ logger.warning(f"Error stopping manager for {manager.project_name}: {e}")
+
+ with _managers_lock:
+ _managers.clear()
diff --git a/server/websocket.py b/server/websocket.py
new file mode 100644
index 0000000..0027127
--- /dev/null
+++ b/server/websocket.py
@@ -0,0 +1,248 @@
+"""
+WebSocket Handlers
+==================
+
+Real-time updates for project progress and agent output.
+"""
+
+import asyncio
+import json
+import logging
+import re
+from datetime import datetime
+from pathlib import Path
+from typing import Set
+
+from fastapi import WebSocket, WebSocketDisconnect
+
+from .services.process_manager import get_manager
+
+# Lazy imports
+_GENERATIONS_DIR = None
+_count_passing_tests = None
+
+logger = logging.getLogger(__name__)
+
+
+def _get_generations_dir():
+ """Lazy import of GENERATIONS_DIR."""
+ global _GENERATIONS_DIR
+ if _GENERATIONS_DIR is None:
+ import sys
+ root = Path(__file__).parent.parent
+ if str(root) not in sys.path:
+ sys.path.insert(0, str(root))
+ from start import GENERATIONS_DIR
+ _GENERATIONS_DIR = GENERATIONS_DIR
+ return _GENERATIONS_DIR
+
+
+def _get_count_passing_tests():
+ """Lazy import of count_passing_tests."""
+ global _count_passing_tests
+ if _count_passing_tests is None:
+ import sys
+ root = Path(__file__).parent.parent
+ if str(root) not in sys.path:
+ sys.path.insert(0, str(root))
+ from progress import count_passing_tests
+ _count_passing_tests = count_passing_tests
+ return _count_passing_tests
+
+
+class ConnectionManager:
+ """Manages WebSocket connections per project."""
+
+ def __init__(self):
+ # project_name -> set of WebSocket connections
+ self.active_connections: dict[str, Set[WebSocket]] = {}
+ self._lock = asyncio.Lock()
+
+ async def connect(self, websocket: WebSocket, project_name: str):
+ """Accept a WebSocket connection for a project."""
+ await websocket.accept()
+
+ async with self._lock:
+ if project_name not in self.active_connections:
+ self.active_connections[project_name] = set()
+ self.active_connections[project_name].add(websocket)
+
+ async def disconnect(self, websocket: WebSocket, project_name: str):
+ """Remove a WebSocket connection."""
+ async with self._lock:
+ if project_name in self.active_connections:
+ self.active_connections[project_name].discard(websocket)
+ if not self.active_connections[project_name]:
+ del self.active_connections[project_name]
+
+ async def broadcast_to_project(self, project_name: str, message: dict):
+ """Broadcast a message to all connections for a project."""
+ async with self._lock:
+ connections = list(self.active_connections.get(project_name, set()))
+
+ dead_connections = []
+
+ for connection in connections:
+ try:
+ await connection.send_json(message)
+ except Exception:
+ dead_connections.append(connection)
+
+ # Clean up dead connections
+ if dead_connections:
+ async with self._lock:
+ for connection in dead_connections:
+ if project_name in self.active_connections:
+ self.active_connections[project_name].discard(connection)
+
+ def get_connection_count(self, project_name: str) -> int:
+ """Get number of active connections for a project."""
+ return len(self.active_connections.get(project_name, set()))
+
+
+# Global connection manager
+manager = ConnectionManager()
+
+# Root directory
+ROOT_DIR = Path(__file__).parent.parent
+
+
+def validate_project_name(name: str) -> bool:
+ """Validate project name to prevent path traversal."""
+ return bool(re.match(r'^[a-zA-Z0-9_-]{1,50}$', name))
+
+
+async def poll_progress(websocket: WebSocket, project_name: str):
+ """Poll database for progress changes and send updates."""
+ project_dir = _get_generations_dir() / project_name
+ count_passing_tests = _get_count_passing_tests()
+ last_passing = -1
+ last_total = -1
+
+ while True:
+ try:
+ passing, total = count_passing_tests(project_dir)
+
+ # Only send if changed
+ if passing != last_passing or total != last_total:
+ last_passing = passing
+ last_total = total
+ percentage = (passing / total * 100) if total > 0 else 0
+
+ await websocket.send_json({
+ "type": "progress",
+ "passing": passing,
+ "total": total,
+ "percentage": round(percentage, 1),
+ })
+
+ await asyncio.sleep(2) # Poll every 2 seconds
+ except asyncio.CancelledError:
+ raise
+ except Exception as e:
+ logger.warning(f"Progress polling error: {e}")
+ break
+
+
+async def project_websocket(websocket: WebSocket, project_name: str):
+ """
+ WebSocket endpoint for project updates.
+
+ Streams:
+ - Progress updates (passing/total counts)
+ - Agent status changes
+ - Agent stdout/stderr lines
+ """
+ if not validate_project_name(project_name):
+ await websocket.close(code=4000, reason="Invalid project name")
+ return
+
+ project_dir = _get_generations_dir() / project_name
+ if not project_dir.exists():
+ await websocket.close(code=4004, reason="Project not found")
+ return
+
+ await manager.connect(websocket, project_name)
+
+ # Get agent manager and register callbacks
+ agent_manager = get_manager(project_name, ROOT_DIR)
+
+ async def on_output(line: str):
+ """Handle agent output - broadcast to this WebSocket."""
+ try:
+ await websocket.send_json({
+ "type": "log",
+ "line": line,
+ "timestamp": datetime.now().isoformat(),
+ })
+ except Exception:
+ pass # Connection may be closed
+
+ async def on_status_change(status: str):
+ """Handle status change - broadcast to this WebSocket."""
+ try:
+ await websocket.send_json({
+ "type": "agent_status",
+ "status": status,
+ })
+ except Exception:
+ pass # Connection may be closed
+
+ # Register callbacks
+ agent_manager.add_output_callback(on_output)
+ agent_manager.add_status_callback(on_status_change)
+
+ # Start progress polling task
+ poll_task = asyncio.create_task(poll_progress(websocket, project_name))
+
+ try:
+ # Send initial status
+ await websocket.send_json({
+ "type": "agent_status",
+ "status": agent_manager.status,
+ })
+
+ # Send initial progress
+ count_passing_tests = _get_count_passing_tests()
+ passing, total = count_passing_tests(project_dir)
+ percentage = (passing / total * 100) if total > 0 else 0
+ await websocket.send_json({
+ "type": "progress",
+ "passing": passing,
+ "total": total,
+ "percentage": round(percentage, 1),
+ })
+
+ # Keep connection alive and handle incoming messages
+ while True:
+ try:
+ # Wait for any incoming messages (ping/pong, commands, etc.)
+ data = await websocket.receive_text()
+ message = json.loads(data)
+
+ # Handle ping
+ if message.get("type") == "ping":
+ await websocket.send_json({"type": "pong"})
+
+ except WebSocketDisconnect:
+ break
+ except json.JSONDecodeError:
+ logger.warning(f"Invalid JSON from WebSocket: {data[:100] if data else 'empty'}")
+ except Exception as e:
+ logger.warning(f"WebSocket error: {e}")
+ break
+
+ finally:
+ # Clean up
+ poll_task.cancel()
+ try:
+ await poll_task
+ except asyncio.CancelledError:
+ pass
+
+ # Unregister callbacks
+ agent_manager.remove_output_callback(on_output)
+ agent_manager.remove_status_callback(on_status_change)
+
+ # Disconnect from manager
+ await manager.disconnect(websocket, project_name)
diff --git a/start_ui.bat b/start_ui.bat
new file mode 100644
index 0000000..a5c58df
--- /dev/null
+++ b/start_ui.bat
@@ -0,0 +1,23 @@
+@echo off
+REM Autonomous Coder UI Launcher for Windows
+REM This script launches the web UI for the autonomous coding agent.
+
+echo.
+echo ====================================
+echo Autonomous Coder UI
+echo ====================================
+echo.
+
+REM Check if Python is available
+where python >nul 2>&1
+if %ERRORLEVEL% neq 0 (
+ echo ERROR: Python not found in PATH
+ echo Please install Python from https://python.org
+ pause
+ exit /b 1
+)
+
+REM Run the Python launcher
+python "%~dp0start_ui.py" %*
+
+pause
diff --git a/start_ui.py b/start_ui.py
new file mode 100644
index 0000000..4ae9acc
--- /dev/null
+++ b/start_ui.py
@@ -0,0 +1,294 @@
+#!/usr/bin/env python3
+"""
+Autonomous Coder UI Launcher
+=============================
+
+Automated launcher that handles all setup:
+1. Creates/activates Python virtual environment
+2. Installs Python dependencies
+3. Checks for Node.js
+4. Installs npm dependencies
+5. Builds React frontend (if needed)
+6. Starts FastAPI server
+7. Opens browser to the UI
+
+Usage:
+ python start_ui.py [--dev]
+
+Options:
+ --dev Run in development mode with Vite hot reload
+"""
+
+import os
+import shutil
+import socket
+import subprocess
+import sys
+import time
+import webbrowser
+from pathlib import Path
+
+
+ROOT = Path(__file__).parent.absolute()
+VENV_DIR = ROOT / "venv"
+UI_DIR = ROOT / "ui"
+
+
+def print_step(step: int, total: int, message: str) -> None:
+ """Print a formatted step message."""
+ print(f"\n[{step}/{total}] {message}")
+ print("-" * 50)
+
+
+def find_available_port(start: int = 8000, max_attempts: int = 10) -> int:
+ """Find an available port starting from the given port."""
+ for port in range(start, start + max_attempts):
+ try:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ s.bind(("127.0.0.1", port))
+ return port
+ except OSError:
+ continue
+ raise RuntimeError(f"No available ports found in range {start}-{start + max_attempts}")
+
+
+def get_venv_python() -> Path:
+ """Get the path to the virtual environment Python executable."""
+ if sys.platform == "win32":
+ return VENV_DIR / "Scripts" / "python.exe"
+ return VENV_DIR / "bin" / "python"
+
+
+def run_command(cmd: list, cwd: Path | None = None, check: bool = True) -> bool:
+ """Run a command and return success status."""
+ try:
+ subprocess.run(cmd, cwd=str(cwd) if cwd else None, check=check)
+ return True
+ except subprocess.CalledProcessError:
+ return False
+ except FileNotFoundError:
+ return False
+
+
+def setup_python_venv() -> bool:
+ """Create Python virtual environment if it doesn't exist."""
+ if VENV_DIR.exists() and get_venv_python().exists():
+ print(" Virtual environment already exists")
+ return True
+
+ print(" Creating virtual environment...")
+ return run_command([sys.executable, "-m", "venv", str(VENV_DIR)])
+
+
+def install_python_deps() -> bool:
+ """Install Python dependencies."""
+ venv_python = get_venv_python()
+ requirements = ROOT / "requirements.txt"
+
+ if not requirements.exists():
+ print(" ERROR: requirements.txt not found")
+ return False
+
+ print(" Installing Python dependencies...")
+ return run_command([
+ str(venv_python), "-m", "pip", "install",
+ "-q", "--upgrade", "pip"
+ ]) and run_command([
+ str(venv_python), "-m", "pip", "install",
+ "-q", "-r", str(requirements)
+ ])
+
+
+def check_node() -> bool:
+ """Check if Node.js is installed."""
+ node = shutil.which("node")
+ npm = shutil.which("npm")
+
+ if not node:
+ print(" ERROR: Node.js not found")
+ print(" Please install Node.js from https://nodejs.org")
+ return False
+
+ if not npm:
+ print(" ERROR: npm not found")
+ print(" Please install Node.js from https://nodejs.org")
+ return False
+
+ # Get version
+ try:
+ result = subprocess.run(
+ ["node", "--version"],
+ capture_output=True,
+ text=True
+ )
+ print(f" Node.js version: {result.stdout.strip()}")
+ except Exception:
+ pass
+
+ return True
+
+
+def install_npm_deps() -> bool:
+ """Install npm dependencies if node_modules doesn't exist."""
+ node_modules = UI_DIR / "node_modules"
+
+ if node_modules.exists():
+ print(" npm dependencies already installed")
+ return True
+
+ print(" Installing npm dependencies (this may take a few minutes)...")
+ npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm"
+ return run_command([npm_cmd, "install"], cwd=UI_DIR)
+
+
+def build_frontend() -> bool:
+ """Build the React frontend if dist doesn't exist."""
+ dist_dir = UI_DIR / "dist"
+
+ if dist_dir.exists():
+ print(" Frontend already built")
+ return True
+
+ print(" Building React frontend...")
+ npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm"
+ return run_command([npm_cmd, "run", "build"], cwd=UI_DIR)
+
+
+def start_dev_server(port: int) -> tuple:
+ """Start both Vite and FastAPI in development mode."""
+ venv_python = get_venv_python()
+
+ print(f"\n Starting development servers...")
+ print(f" - FastAPI backend: http://127.0.0.1:{port}")
+ print(f" - Vite frontend: http://127.0.0.1:5173")
+
+ # Start FastAPI
+ backend = subprocess.Popen([
+ str(venv_python), "-m", "uvicorn",
+ "server.main:app",
+ "--host", "127.0.0.1",
+ "--port", str(port),
+ "--reload"
+ ], cwd=str(ROOT))
+
+ # Start Vite with API port env var for proxy configuration
+ npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm"
+ vite_env = os.environ.copy()
+ vite_env["VITE_API_PORT"] = str(port)
+ frontend = subprocess.Popen([
+ npm_cmd, "run", "dev"
+ ], cwd=str(UI_DIR), env=vite_env)
+
+ return backend, frontend
+
+
+def start_production_server(port: int):
+ """Start FastAPI server in production mode."""
+ venv_python = get_venv_python()
+
+ print(f"\n Starting server at http://127.0.0.1:{port}")
+
+ return subprocess.Popen([
+ str(venv_python), "-m", "uvicorn",
+ "server.main:app",
+ "--host", "127.0.0.1",
+ "--port", str(port)
+ ], cwd=str(ROOT))
+
+
+def main() -> None:
+ """Main entry point."""
+ dev_mode = "--dev" in sys.argv
+
+ print("=" * 50)
+ print(" Autonomous Coder UI Setup")
+ print("=" * 50)
+
+ total_steps = 6 if not dev_mode else 5
+
+ # Step 1: Python venv
+ print_step(1, total_steps, "Setting up Python environment")
+ if not setup_python_venv():
+ print("ERROR: Failed to create virtual environment")
+ sys.exit(1)
+
+ # Step 2: Python dependencies
+ print_step(2, total_steps, "Installing Python dependencies")
+ if not install_python_deps():
+ print("ERROR: Failed to install Python dependencies")
+ sys.exit(1)
+
+ # Step 3: Check Node.js
+ print_step(3, total_steps, "Checking Node.js")
+ if not check_node():
+ sys.exit(1)
+
+ # Step 4: npm dependencies
+ print_step(4, total_steps, "Installing npm dependencies")
+ if not install_npm_deps():
+ print("ERROR: Failed to install npm dependencies")
+ sys.exit(1)
+
+ # Step 5: Build frontend (production only)
+ if not dev_mode:
+ print_step(5, total_steps, "Building frontend")
+ if not build_frontend():
+ print("ERROR: Failed to build frontend")
+ sys.exit(1)
+
+ # Step 6: Start server
+ step = 5 if dev_mode else 6
+ print_step(step, total_steps, "Starting server")
+
+ port = find_available_port()
+
+ try:
+ if dev_mode:
+ backend, frontend = start_dev_server(port)
+
+ # Open browser to Vite dev server
+ time.sleep(3)
+ webbrowser.open("http://127.0.0.1:5173")
+
+ print("\n" + "=" * 50)
+ print(" Development mode active")
+ print(" Press Ctrl+C to stop")
+ print("=" * 50)
+
+ try:
+ # Wait for either process to exit
+ while backend.poll() is None and frontend.poll() is None:
+ time.sleep(1)
+ except KeyboardInterrupt:
+ print("\n\nShutting down...")
+ finally:
+ backend.terminate()
+ frontend.terminate()
+ backend.wait()
+ frontend.wait()
+ else:
+ server = start_production_server(port)
+
+ # Open browser
+ time.sleep(2)
+ webbrowser.open(f"http://127.0.0.1:{port}")
+
+ print("\n" + "=" * 50)
+ print(f" Server running at http://127.0.0.1:{port}")
+ print(" Press Ctrl+C to stop")
+ print("=" * 50)
+
+ try:
+ server.wait()
+ except KeyboardInterrupt:
+ print("\n\nShutting down...")
+ server.terminate()
+ server.wait()
+
+ except Exception as e:
+ print(f"\nERROR: {e}")
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/start_ui.sh b/start_ui.sh
new file mode 100644
index 0000000..317d744
--- /dev/null
+++ b/start_ui.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# Autonomous Coder UI Launcher for Unix/Linux/macOS
+# This script launches the web UI for the autonomous coding agent.
+
+echo ""
+echo "===================================="
+echo " Autonomous Coder UI"
+echo "===================================="
+echo ""
+
+# Get the directory where this script is located
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Check if Python is available
+if ! command -v python3 &> /dev/null; then
+ if ! command -v python &> /dev/null; then
+ echo "ERROR: Python not found"
+ echo "Please install Python from https://python.org"
+ exit 1
+ fi
+ PYTHON_CMD="python"
+else
+ PYTHON_CMD="python3"
+fi
+
+# Run the Python launcher
+$PYTHON_CMD "$SCRIPT_DIR/start_ui.py" "$@"
diff --git a/ui/index.html b/ui/index.html
new file mode 100644
index 0000000..67b7ff2
--- /dev/null
+++ b/ui/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ Autonomous Coder
+
+
+
+
+
+
+
+
+
diff --git a/ui/package-lock.json b/ui/package-lock.json
new file mode 100644
index 0000000..c2e5fc3
--- /dev/null
+++ b/ui/package-lock.json
@@ -0,0 +1,4570 @@
+{
+ "name": "autonomous-coding-ui",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "autonomous-coding-ui",
+ "version": "1.0.0",
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-dropdown-menu": "^2.1.2",
+ "@radix-ui/react-tooltip": "^1.1.3",
+ "@tanstack/react-query": "^5.60.0",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.460.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.13.0",
+ "@tailwindcss/vite": "^4.0.0-beta.4",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.3",
+ "eslint": "^9.13.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.14",
+ "globals": "^15.11.0",
+ "tailwindcss": "^4.0.0-beta.4",
+ "typescript": "~5.6.2",
+ "typescript-eslint": "^8.11.0",
+ "vite": "^5.4.10"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
+ "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
+ "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.2"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
+ "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz",
+ "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "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/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@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/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-tooltip": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz",
+ "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-visually-hidden": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
+ "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz",
+ "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz",
+ "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz",
+ "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz",
+ "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz",
+ "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz",
+ "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz",
+ "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz",
+ "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz",
+ "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz",
+ "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz",
+ "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz",
+ "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz",
+ "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz",
+ "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz",
+ "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz",
+ "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz",
+ "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz",
+ "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz",
+ "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz",
+ "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz",
+ "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz",
+ "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.4",
+ "enhanced-resolve": "^5.18.3",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.30.2",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz",
+ "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 10"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-arm64": "4.1.18",
+ "@tailwindcss/oxide-darwin-x64": "4.1.18",
+ "@tailwindcss/oxide-freebsd-x64": "4.1.18",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.1.18",
+ "@tailwindcss/oxide-linux-x64-musl": "4.1.18",
+ "@tailwindcss/oxide-wasm32-wasi": "4.1.18",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.1.18"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz",
+ "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz",
+ "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz",
+ "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz",
+ "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz",
+ "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz",
+ "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz",
+ "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz",
+ "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz",
+ "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz",
+ "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.0",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.4.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz",
+ "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz",
+ "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz",
+ "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.1.18",
+ "@tailwindcss/oxide": "4.1.18",
+ "tailwindcss": "4.1.18"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7"
+ }
+ },
+ "node_modules/@tanstack/query-core": {
+ "version": "5.90.15",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.15.tgz",
+ "integrity": "sha512-mInIZNUZftbERE+/Hbtswfse49uUQwch46p+27gP9DWJL927UjnaWEF2t3RMOqBcXbfMdcNkPe06VyUIAZTV1g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.90.15",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.15.tgz",
+ "integrity": "sha512-uQvnDDcTOgJouNtAyrgRej+Azf0U5WDov3PXmHFUBc+t1INnAYhIlpZtCGNBLwCN41b43yO7dPNZu8xWkUFBwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.90.15"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "devOptional": 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==",
+ "devOptional": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz",
+ "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/type-utils": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
+ "ignore": "^7.0.0",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.51.0",
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz",
+ "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz",
+ "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.51.0",
+ "@typescript-eslint/types": "^8.51.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz",
+ "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz",
+ "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz",
+ "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^2.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz",
+ "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz",
+ "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.51.0",
+ "@typescript-eslint/tsconfig-utils": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/visitor-keys": "8.51.0",
+ "debug": "^4.3.4",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz",
+ "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.7.0",
+ "@typescript-eslint/scope-manager": "8.51.0",
+ "@typescript-eslint/types": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz",
+ "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.51.0",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
+ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.11",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz",
+ "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "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.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001762",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
+ "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
+ "dev": true,
+ "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/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "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/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "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==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.267",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+ "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.18.4",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz",
+ "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "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": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.2",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz",
+ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.1",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.1",
+ "@eslint/js": "9.39.2",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
+ "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.4.26",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz",
+ "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": ">=8.40"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
+ "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "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/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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/globals": {
+ "version": "15.15.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
+ "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "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/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "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/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz",
+ "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.30.2",
+ "lightningcss-darwin-arm64": "1.30.2",
+ "lightningcss-darwin-x64": "1.30.2",
+ "lightningcss-freebsd-x64": "1.30.2",
+ "lightningcss-linux-arm-gnueabihf": "1.30.2",
+ "lightningcss-linux-arm64-gnu": "1.30.2",
+ "lightningcss-linux-arm64-musl": "1.30.2",
+ "lightningcss-linux-x64-gnu": "1.30.2",
+ "lightningcss-linux-x64-musl": "1.30.2",
+ "lightningcss-win32-arm64-msvc": "1.30.2",
+ "lightningcss-win32-x64-msvc": "1.30.2"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz",
+ "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz",
+ "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz",
+ "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz",
+ "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz",
+ "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz",
+ "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz",
+ "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz",
+ "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz",
+ "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz",
+ "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.30.2",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz",
+ "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "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/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.460.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz",
+ "integrity": "sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "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/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "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/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "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-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz",
+ "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.54.0",
+ "@rollup/rollup-android-arm64": "4.54.0",
+ "@rollup/rollup-darwin-arm64": "4.54.0",
+ "@rollup/rollup-darwin-x64": "4.54.0",
+ "@rollup/rollup-freebsd-arm64": "4.54.0",
+ "@rollup/rollup-freebsd-x64": "4.54.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.54.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.54.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.54.0",
+ "@rollup/rollup-linux-arm64-musl": "4.54.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.54.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.54.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.54.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.54.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.54.0",
+ "@rollup/rollup-linux-x64-gnu": "4.54.0",
+ "@rollup/rollup-linux-x64-musl": "4.54.0",
+ "@rollup/rollup-openharmony-arm64": "4.54.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.54.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.54.0",
+ "@rollup/rollup-win32-x64-gnu": "4.54.0",
+ "@rollup/rollup-win32-x64-msvc": "4.54.0",
+ "fsevents": "~2.3.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/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.1.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
+ "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
+ "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "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/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.51.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz",
+ "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.51.0",
+ "@typescript-eslint/parser": "8.51.0",
+ "@typescript-eslint/typescript-estree": "8.51.0",
+ "@typescript-eslint/utils": "8.51.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "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/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/ui/package.json b/ui/package.json
new file mode 100644
index 0000000..81ba088
--- /dev/null
+++ b/ui/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "autonomous-coding-ui",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc -b && vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-dropdown-menu": "^2.1.2",
+ "@radix-ui/react-tooltip": "^1.1.3",
+ "@tanstack/react-query": "^5.60.0",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.460.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^9.13.0",
+ "@tailwindcss/vite": "^4.0.0-beta.4",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^4.3.3",
+ "eslint": "^9.13.0",
+ "eslint-plugin-react-hooks": "^5.0.0",
+ "eslint-plugin-react-refresh": "^0.4.14",
+ "globals": "^15.11.0",
+ "tailwindcss": "^4.0.0-beta.4",
+ "typescript": "~5.6.2",
+ "typescript-eslint": "^8.11.0",
+ "vite": "^5.4.10"
+ }
+}
diff --git a/ui/public/vite.svg b/ui/public/vite.svg
new file mode 100644
index 0000000..da73808
--- /dev/null
+++ b/ui/public/vite.svg
@@ -0,0 +1,6 @@
+
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
new file mode 100644
index 0000000..8d67635
--- /dev/null
+++ b/ui/src/App.tsx
@@ -0,0 +1,130 @@
+import { useState } from 'react'
+import { useProjects, useFeatures } from './hooks/useProjects'
+import { useProjectWebSocket } from './hooks/useWebSocket'
+import { ProjectSelector } from './components/ProjectSelector'
+import { KanbanBoard } from './components/KanbanBoard'
+import { AgentControl } from './components/AgentControl'
+import { ProgressDashboard } from './components/ProgressDashboard'
+import { SetupWizard } from './components/SetupWizard'
+import { AddFeatureForm } from './components/AddFeatureForm'
+import { FeatureModal } from './components/FeatureModal'
+import { Plus } from 'lucide-react'
+import type { Feature } from './lib/types'
+
+function App() {
+ const [selectedProject, setSelectedProject] = useState(null)
+ const [showAddFeature, setShowAddFeature] = useState(false)
+ const [selectedFeature, setSelectedFeature] = useState(null)
+ const [setupComplete, setSetupComplete] = useState(true) // Start optimistic
+
+ const { data: projects, isLoading: projectsLoading } = useProjects()
+ const { data: features } = useFeatures(selectedProject)
+ const wsState = useProjectWebSocket(selectedProject)
+
+ // Combine WebSocket progress with feature data
+ const progress = wsState.progress.total > 0 ? wsState.progress : {
+ passing: features?.done.length ?? 0,
+ total: (features?.pending.length ?? 0) + (features?.in_progress.length ?? 0) + (features?.done.length ?? 0),
+ percentage: 0,
+ }
+
+ if (progress.total > 0 && progress.percentage === 0) {
+ progress.percentage = Math.round((progress.passing / progress.total) * 100 * 10) / 10
+ }
+
+ if (!setupComplete) {
+ return setSetupComplete(true)} />
+ }
+
+ return (
+
+ {/* Header */}
+
+
+
+ {/* Logo and Title */}
+
+ Autonomous Coder
+
+
+ {/* Controls */}
+
+
+
+ {selectedProject && (
+ <>
+
+
+
+ >
+ )}
+
+
+
+
+
+ {/* Main Content */}
+
+ {!selectedProject ? (
+
+
+ Welcome to Autonomous Coder
+
+
+ Select a project from the dropdown above or create a new one to get started.
+
+
+ ) : (
+
+ {/* Progress Dashboard */}
+
+
+ {/* Kanban Board */}
+
+
+ )}
+
+
+ {/* Add Feature Modal */}
+ {showAddFeature && selectedProject && (
+
setShowAddFeature(false)}
+ />
+ )}
+
+ {/* Feature Detail Modal */}
+ {selectedFeature && selectedProject && (
+ setSelectedFeature(null)}
+ />
+ )}
+
+ )
+}
+
+export default App
diff --git a/ui/src/components/AddFeatureForm.tsx b/ui/src/components/AddFeatureForm.tsx
new file mode 100644
index 0000000..be0c3cd
--- /dev/null
+++ b/ui/src/components/AddFeatureForm.tsx
@@ -0,0 +1,213 @@
+import { useState, useId } from 'react'
+import { X, Plus, Trash2, Loader2, AlertCircle } from 'lucide-react'
+import { useCreateFeature } from '../hooks/useProjects'
+
+interface Step {
+ id: string
+ value: string
+}
+
+interface AddFeatureFormProps {
+ projectName: string
+ onClose: () => void
+}
+
+export function AddFeatureForm({ projectName, onClose }: AddFeatureFormProps) {
+ const formId = useId()
+ const [category, setCategory] = useState('')
+ const [name, setName] = useState('')
+ const [description, setDescription] = useState('')
+ const [steps, setSteps] = useState([{ id: `${formId}-step-0`, value: '' }])
+ const [error, setError] = useState(null)
+ const [stepCounter, setStepCounter] = useState(1)
+
+ const createFeature = useCreateFeature(projectName)
+
+ const handleAddStep = () => {
+ setSteps([...steps, { id: `${formId}-step-${stepCounter}`, value: '' }])
+ setStepCounter(stepCounter + 1)
+ }
+
+ const handleRemoveStep = (id: string) => {
+ setSteps(steps.filter(step => step.id !== id))
+ }
+
+ const handleStepChange = (id: string, value: string) => {
+ setSteps(steps.map(step =>
+ step.id === id ? { ...step, value } : step
+ ))
+ }
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault()
+ setError(null)
+
+ // Filter out empty steps
+ const filteredSteps = steps
+ .map(s => s.value.trim())
+ .filter(s => s.length > 0)
+
+ try {
+ await createFeature.mutateAsync({
+ category: category.trim(),
+ name: name.trim(),
+ description: description.trim(),
+ steps: filteredSteps,
+ })
+ onClose()
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to create feature')
+ }
+ }
+
+ const isValid = category.trim() && name.trim() && description.trim()
+
+ return (
+
+
e.stopPropagation()}
+ >
+ {/* Header */}
+
+
+ Add Feature
+
+
+
+
+ {/* Form */}
+
+
+
+ )
+}
diff --git a/ui/src/components/AgentControl.tsx b/ui/src/components/AgentControl.tsx
new file mode 100644
index 0000000..5356f87
--- /dev/null
+++ b/ui/src/components/AgentControl.tsx
@@ -0,0 +1,144 @@
+import { Play, Pause, Square, Loader2 } from 'lucide-react'
+import {
+ useStartAgent,
+ useStopAgent,
+ usePauseAgent,
+ useResumeAgent,
+} from '../hooks/useProjects'
+import type { AgentStatus } from '../lib/types'
+
+interface AgentControlProps {
+ projectName: string
+ status: AgentStatus
+}
+
+export function AgentControl({ projectName, status }: AgentControlProps) {
+ const startAgent = useStartAgent(projectName)
+ const stopAgent = useStopAgent(projectName)
+ const pauseAgent = usePauseAgent(projectName)
+ const resumeAgent = useResumeAgent(projectName)
+
+ const isLoading =
+ startAgent.isPending ||
+ stopAgent.isPending ||
+ pauseAgent.isPending ||
+ resumeAgent.isPending
+
+ const handleStart = () => startAgent.mutate()
+ const handleStop = () => stopAgent.mutate()
+ const handlePause = () => pauseAgent.mutate()
+ const handleResume = () => resumeAgent.mutate()
+
+ return (
+
+ {/* Status Indicator */}
+
+
+ {/* Control Buttons */}
+
+ {status === 'stopped' || status === 'crashed' ? (
+
+ ) : status === 'running' ? (
+ <>
+
+
+ >
+ ) : status === 'paused' ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+ )
+}
+
+function StatusIndicator({ status }: { status: AgentStatus }) {
+ const statusConfig = {
+ stopped: {
+ color: 'var(--color-neo-text-secondary)',
+ label: 'Stopped',
+ pulse: false,
+ },
+ running: {
+ color: 'var(--color-neo-done)',
+ label: 'Running',
+ pulse: true,
+ },
+ paused: {
+ color: 'var(--color-neo-pending)',
+ label: 'Paused',
+ pulse: false,
+ },
+ crashed: {
+ color: 'var(--color-neo-danger)',
+ label: 'Crashed',
+ pulse: true,
+ },
+ }
+
+ const config = statusConfig[status]
+
+ return (
+
+
+
+ {config.label}
+
+
+ )
+}
diff --git a/ui/src/components/FeatureCard.tsx b/ui/src/components/FeatureCard.tsx
new file mode 100644
index 0000000..c7190fb
--- /dev/null
+++ b/ui/src/components/FeatureCard.tsx
@@ -0,0 +1,86 @@
+import { CheckCircle2, Circle, Loader2 } from 'lucide-react'
+import type { Feature } from '../lib/types'
+
+interface FeatureCardProps {
+ feature: Feature
+ onClick: () => void
+ isInProgress?: boolean
+}
+
+// Generate consistent color for category
+function getCategoryColor(category: string): string {
+ const colors = [
+ '#ff006e', // pink
+ '#00b4d8', // cyan
+ '#70e000', // green
+ '#ffd60a', // yellow
+ '#ff5400', // orange
+ '#8338ec', // purple
+ '#3a86ff', // blue
+ ]
+
+ let hash = 0
+ for (let i = 0; i < category.length; i++) {
+ hash = category.charCodeAt(i) + ((hash << 5) - hash)
+ }
+
+ return colors[Math.abs(hash) % colors.length]
+}
+
+export function FeatureCard({ feature, onClick, isInProgress }: FeatureCardProps) {
+ const categoryColor = getCategoryColor(feature.category)
+
+ return (
+
+ )
+}
diff --git a/ui/src/components/FeatureModal.tsx b/ui/src/components/FeatureModal.tsx
new file mode 100644
index 0000000..6daede1
--- /dev/null
+++ b/ui/src/components/FeatureModal.tsx
@@ -0,0 +1,190 @@
+import { useState } from 'react'
+import { X, CheckCircle2, Circle, SkipForward, Trash2, Loader2, AlertCircle } from 'lucide-react'
+import { useSkipFeature, useDeleteFeature } from '../hooks/useProjects'
+import type { Feature } from '../lib/types'
+
+interface FeatureModalProps {
+ feature: Feature
+ projectName: string
+ onClose: () => void
+}
+
+export function FeatureModal({ feature, projectName, onClose }: FeatureModalProps) {
+ const [error, setError] = useState(null)
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
+
+ const skipFeature = useSkipFeature(projectName)
+ const deleteFeature = useDeleteFeature(projectName)
+
+ const handleSkip = async () => {
+ setError(null)
+ try {
+ await skipFeature.mutateAsync(feature.id)
+ onClose()
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to skip feature')
+ }
+ }
+
+ const handleDelete = async () => {
+ setError(null)
+ try {
+ await deleteFeature.mutateAsync(feature.id)
+ onClose()
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to delete feature')
+ }
+ }
+
+ return (
+
+
e.stopPropagation()}
+ >
+ {/* Header */}
+
+
+
+ {feature.category}
+
+
+ {feature.name}
+
+
+
+
+
+ {/* Content */}
+
+ {/* Error Message */}
+ {error && (
+
+
+
{error}
+
+
+ )}
+
+ {/* Status */}
+
+ {feature.passes ? (
+ <>
+
+
+ COMPLETE
+
+ >
+ ) : (
+ <>
+
+
+ PENDING
+
+ >
+ )}
+
+ Priority: #{feature.priority}
+
+
+
+ {/* Description */}
+
+
+ Description
+
+
+ {feature.description}
+
+
+
+ {/* Steps */}
+ {feature.steps.length > 0 && (
+
+
+ Test Steps
+
+
+ {feature.steps.map((step, index) => (
+ -
+ {step}
+
+ ))}
+
+
+ )}
+
+
+ {/* Actions */}
+ {!feature.passes && (
+
+ {showDeleteConfirm ? (
+
+
+ Are you sure you want to delete this feature?
+
+
+
+
+
+
+ ) : (
+
+
+
+
+ )}
+
+ )}
+
+
+ )
+}
diff --git a/ui/src/components/KanbanBoard.tsx b/ui/src/components/KanbanBoard.tsx
new file mode 100644
index 0000000..d070c70
--- /dev/null
+++ b/ui/src/components/KanbanBoard.tsx
@@ -0,0 +1,52 @@
+import { KanbanColumn } from './KanbanColumn'
+import type { Feature, FeatureListResponse } from '../lib/types'
+
+interface KanbanBoardProps {
+ features: FeatureListResponse | undefined
+ onFeatureClick: (feature: Feature) => void
+}
+
+export function KanbanBoard({ features, onFeatureClick }: KanbanBoardProps) {
+ if (!features) {
+ return (
+
+ {['Pending', 'In Progress', 'Done'].map(title => (
+
+
+
+ {[1, 2, 3].map(i => (
+
+ ))}
+
+
+ ))}
+
+ )
+ }
+
+ return (
+
+
+
+
+
+ )
+}
diff --git a/ui/src/components/KanbanColumn.tsx b/ui/src/components/KanbanColumn.tsx
new file mode 100644
index 0000000..b1486d5
--- /dev/null
+++ b/ui/src/components/KanbanColumn.tsx
@@ -0,0 +1,65 @@
+import { FeatureCard } from './FeatureCard'
+import type { Feature } from '../lib/types'
+
+interface KanbanColumnProps {
+ title: string
+ count: number
+ features: Feature[]
+ color: 'pending' | 'progress' | 'done'
+ onFeatureClick: (feature: Feature) => void
+}
+
+const colorMap = {
+ pending: 'var(--color-neo-pending)',
+ progress: 'var(--color-neo-progress)',
+ done: 'var(--color-neo-done)',
+}
+
+export function KanbanColumn({
+ title,
+ count,
+ features,
+ color,
+ onFeatureClick,
+}: KanbanColumnProps) {
+ return (
+
+ {/* Header */}
+
+
+ {title}
+ {count}
+
+
+
+ {/* Cards */}
+
+ {features.length === 0 ? (
+
+ No features
+
+ ) : (
+ features.map((feature, index) => (
+
+ onFeatureClick(feature)}
+ isInProgress={color === 'progress'}
+ />
+
+ ))
+ )}
+
+
+ )
+}
diff --git a/ui/src/components/ProgressDashboard.tsx b/ui/src/components/ProgressDashboard.tsx
new file mode 100644
index 0000000..2a85812
--- /dev/null
+++ b/ui/src/components/ProgressDashboard.tsx
@@ -0,0 +1,77 @@
+import { Wifi, WifiOff } from 'lucide-react'
+
+interface ProgressDashboardProps {
+ passing: number
+ total: number
+ percentage: number
+ isConnected: boolean
+}
+
+export function ProgressDashboard({
+ passing,
+ total,
+ percentage,
+ isConnected,
+}: ProgressDashboardProps) {
+ return (
+
+
+
+ Progress
+
+
+ {isConnected ? (
+ <>
+
+ Live
+ >
+ ) : (
+ <>
+
+ Offline
+ >
+ )}
+
+
+
+ {/* Large Percentage */}
+
+
+ {percentage.toFixed(1)}
+
+
+ %
+
+
+
+ {/* Progress Bar */}
+
+
+ {/* Stats */}
+
+
+
+ {passing}
+
+
+ Passing
+
+
+
/
+
+
+ {total}
+
+
+ Total
+
+
+
+
+ )
+}
diff --git a/ui/src/components/ProjectSelector.tsx b/ui/src/components/ProjectSelector.tsx
new file mode 100644
index 0000000..cf7657a
--- /dev/null
+++ b/ui/src/components/ProjectSelector.tsx
@@ -0,0 +1,172 @@
+import { useState } from 'react'
+import { ChevronDown, Plus, FolderOpen, Loader2 } from 'lucide-react'
+import { useCreateProject } from '../hooks/useProjects'
+import type { ProjectSummary } from '../lib/types'
+
+interface ProjectSelectorProps {
+ projects: ProjectSummary[]
+ selectedProject: string | null
+ onSelectProject: (name: string | null) => void
+ isLoading: boolean
+}
+
+export function ProjectSelector({
+ projects,
+ selectedProject,
+ onSelectProject,
+ isLoading,
+}: ProjectSelectorProps) {
+ const [isOpen, setIsOpen] = useState(false)
+ const [showCreate, setShowCreate] = useState(false)
+ const [newProjectName, setNewProjectName] = useState('')
+
+ const createProject = useCreateProject()
+
+ const handleCreateProject = async (e: React.FormEvent) => {
+ e.preventDefault()
+ if (!newProjectName.trim()) return
+
+ try {
+ const project = await createProject.mutateAsync({
+ name: newProjectName.trim(),
+ specMethod: 'manual',
+ })
+ onSelectProject(project.name)
+ setNewProjectName('')
+ setShowCreate(false)
+ setIsOpen(false)
+ } catch (error) {
+ console.error('Failed to create project:', error)
+ }
+ }
+
+ const selectedProjectData = projects.find(p => p.name === selectedProject)
+
+ return (
+
+ {/* Dropdown Trigger */}
+
+
+ {/* Dropdown Menu */}
+ {isOpen && (
+ <>
+ {/* Backdrop */}
+
setIsOpen(false)}
+ />
+
+ {/* Menu */}
+
+ {projects.length > 0 ? (
+
+ {projects.map(project => (
+
+ ))}
+
+ ) : (
+
+ No projects yet
+
+ )}
+
+ {/* Divider */}
+
+
+ {/* Create New */}
+ {showCreate ? (
+
+ ) : (
+
+ )}
+
+ >
+ )}
+
+ )
+}
diff --git a/ui/src/components/SetupWizard.tsx b/ui/src/components/SetupWizard.tsx
new file mode 100644
index 0000000..58dab7b
--- /dev/null
+++ b/ui/src/components/SetupWizard.tsx
@@ -0,0 +1,183 @@
+import { useEffect, useCallback } from 'react'
+import { CheckCircle2, XCircle, Loader2, ExternalLink } from 'lucide-react'
+import { useSetupStatus, useHealthCheck } from '../hooks/useProjects'
+
+interface SetupWizardProps {
+ onComplete: () => void
+}
+
+export function SetupWizard({ onComplete }: SetupWizardProps) {
+ const { data: setupStatus, isLoading: setupLoading, error: setupError } = useSetupStatus()
+ const { data: health, error: healthError } = useHealthCheck()
+
+ const isApiHealthy = health?.status === 'healthy' && !healthError
+ const isReady = isApiHealthy && setupStatus?.claude_cli && setupStatus?.credentials
+
+ // Memoize the completion check to avoid infinite loops
+ const checkAndComplete = useCallback(() => {
+ if (isReady) {
+ onComplete()
+ }
+ }, [isReady, onComplete])
+
+ // Auto-complete if everything is ready
+ useEffect(() => {
+ checkAndComplete()
+ }, [checkAndComplete])
+
+ return (
+
+
+
+ Setup Wizard
+
+
+ Let's make sure everything is ready to go
+
+
+
+ {/* API Health */}
+
+
+ {/* Claude CLI */}
+
+
+ {/* Credentials */}
+
+
+ {/* Node.js */}
+
+
+
+ {/* Continue Button */}
+ {isReady && (
+
+ )}
+
+ {/* Error Message */}
+ {(healthError || setupError) && (
+
+
Setup Error
+
+ {healthError
+ ? 'Cannot connect to the backend server. Make sure to run start_ui.py first.'
+ : 'Failed to check setup status.'}
+
+
+ )}
+
+
+ )
+}
+
+interface SetupItemProps {
+ label: string
+ description: string
+ status: 'success' | 'error' | 'warning' | 'loading'
+ helpLink?: string
+ helpText?: string
+ optional?: boolean
+}
+
+function SetupItem({
+ label,
+ description,
+ status,
+ helpLink,
+ helpText,
+ optional,
+}: SetupItemProps) {
+ return (
+
+ {/* Status Icon */}
+
+ {status === 'success' ? (
+
+ ) : status === 'error' ? (
+
+ ) : status === 'warning' ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Content */}
+
+
+ {label}
+ {optional && (
+
+ (optional)
+
+ )}
+
+
+ {description}
+
+ {(status === 'error' || status === 'warning') && helpLink && (
+
+ {helpText}
+
+ )}
+
+
+ )
+}
diff --git a/ui/src/hooks/useProjects.ts b/ui/src/hooks/useProjects.ts
new file mode 100644
index 0000000..0292d83
--- /dev/null
+++ b/ui/src/hooks/useProjects.ts
@@ -0,0 +1,172 @@
+/**
+ * React Query hooks for project data
+ */
+
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
+import * as api from '../lib/api'
+import type { FeatureCreate } from '../lib/types'
+
+// ============================================================================
+// Projects
+// ============================================================================
+
+export function useProjects() {
+ return useQuery({
+ queryKey: ['projects'],
+ queryFn: api.listProjects,
+ })
+}
+
+export function useProject(name: string | null) {
+ return useQuery({
+ queryKey: ['project', name],
+ queryFn: () => api.getProject(name!),
+ enabled: !!name,
+ })
+}
+
+export function useCreateProject() {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: ({ name, specMethod }: { name: string; specMethod?: 'claude' | 'manual' }) =>
+ api.createProject(name, specMethod),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['projects'] })
+ },
+ })
+}
+
+export function useDeleteProject() {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (name: string) => api.deleteProject(name),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['projects'] })
+ },
+ })
+}
+
+// ============================================================================
+// Features
+// ============================================================================
+
+export function useFeatures(projectName: string | null) {
+ return useQuery({
+ queryKey: ['features', projectName],
+ queryFn: () => api.listFeatures(projectName!),
+ enabled: !!projectName,
+ refetchInterval: 5000, // Refetch every 5 seconds for real-time updates
+ })
+}
+
+export function useCreateFeature(projectName: string) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (feature: FeatureCreate) => api.createFeature(projectName, feature),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['features', projectName] })
+ },
+ })
+}
+
+export function useDeleteFeature(projectName: string) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (featureId: number) => api.deleteFeature(projectName, featureId),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['features', projectName] })
+ },
+ })
+}
+
+export function useSkipFeature(projectName: string) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: (featureId: number) => api.skipFeature(projectName, featureId),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['features', projectName] })
+ },
+ })
+}
+
+// ============================================================================
+// Agent
+// ============================================================================
+
+export function useAgentStatus(projectName: string | null) {
+ return useQuery({
+ queryKey: ['agent-status', projectName],
+ queryFn: () => api.getAgentStatus(projectName!),
+ enabled: !!projectName,
+ refetchInterval: 3000, // Poll every 3 seconds
+ })
+}
+
+export function useStartAgent(projectName: string) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => api.startAgent(projectName),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
+ },
+ })
+}
+
+export function useStopAgent(projectName: string) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => api.stopAgent(projectName),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
+ },
+ })
+}
+
+export function usePauseAgent(projectName: string) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => api.pauseAgent(projectName),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
+ },
+ })
+}
+
+export function useResumeAgent(projectName: string) {
+ const queryClient = useQueryClient()
+
+ return useMutation({
+ mutationFn: () => api.resumeAgent(projectName),
+ onSuccess: () => {
+ queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
+ },
+ })
+}
+
+// ============================================================================
+// Setup
+// ============================================================================
+
+export function useSetupStatus() {
+ return useQuery({
+ queryKey: ['setup-status'],
+ queryFn: api.getSetupStatus,
+ staleTime: 60000, // Cache for 1 minute
+ })
+}
+
+export function useHealthCheck() {
+ return useQuery({
+ queryKey: ['health'],
+ queryFn: api.healthCheck,
+ retry: false,
+ })
+}
diff --git a/ui/src/hooks/useWebSocket.ts b/ui/src/hooks/useWebSocket.ts
new file mode 100644
index 0000000..973624b
--- /dev/null
+++ b/ui/src/hooks/useWebSocket.ts
@@ -0,0 +1,161 @@
+/**
+ * WebSocket Hook for Real-time Updates
+ */
+
+import { useEffect, useRef, useState, useCallback } from 'react'
+import type { WSMessage, AgentStatus } from '../lib/types'
+
+interface WebSocketState {
+ progress: {
+ passing: number
+ total: number
+ percentage: number
+ }
+ agentStatus: AgentStatus
+ logs: Array<{ line: string; timestamp: string }>
+ isConnected: boolean
+}
+
+const MAX_LOGS = 100 // Keep last 100 log lines
+
+export function useProjectWebSocket(projectName: string | null) {
+ const [state, setState] = useState
({
+ progress: { passing: 0, total: 0, percentage: 0 },
+ agentStatus: 'stopped',
+ logs: [],
+ isConnected: false,
+ })
+
+ const wsRef = useRef(null)
+ const reconnectTimeoutRef = useRef(null)
+ const reconnectAttempts = useRef(0)
+
+ const connect = useCallback(() => {
+ if (!projectName) return
+
+ // Build WebSocket URL
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
+ const host = window.location.host
+ const wsUrl = `${protocol}//${host}/ws/projects/${encodeURIComponent(projectName)}`
+
+ try {
+ const ws = new WebSocket(wsUrl)
+ wsRef.current = ws
+
+ ws.onopen = () => {
+ setState(prev => ({ ...prev, isConnected: true }))
+ reconnectAttempts.current = 0
+ }
+
+ ws.onmessage = (event) => {
+ try {
+ const message: WSMessage = JSON.parse(event.data)
+
+ switch (message.type) {
+ case 'progress':
+ setState(prev => ({
+ ...prev,
+ progress: {
+ passing: message.passing,
+ total: message.total,
+ percentage: message.percentage,
+ },
+ }))
+ break
+
+ case 'agent_status':
+ setState(prev => ({
+ ...prev,
+ agentStatus: message.status,
+ }))
+ break
+
+ case 'log':
+ setState(prev => ({
+ ...prev,
+ logs: [
+ ...prev.logs.slice(-MAX_LOGS + 1),
+ { line: message.line, timestamp: message.timestamp },
+ ],
+ }))
+ break
+
+ case 'feature_update':
+ // Feature updates will trigger a refetch via React Query
+ break
+
+ case 'pong':
+ // Heartbeat response
+ break
+ }
+ } catch {
+ console.error('Failed to parse WebSocket message')
+ }
+ }
+
+ ws.onclose = () => {
+ setState(prev => ({ ...prev, isConnected: false }))
+ wsRef.current = null
+
+ // Exponential backoff reconnection
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts.current), 30000)
+ reconnectAttempts.current++
+
+ reconnectTimeoutRef.current = window.setTimeout(() => {
+ connect()
+ }, delay)
+ }
+
+ ws.onerror = () => {
+ ws.close()
+ }
+ } catch {
+ // Failed to connect, will retry via onclose
+ }
+ }, [projectName])
+
+ // Send ping to keep connection alive
+ const sendPing = useCallback(() => {
+ if (wsRef.current?.readyState === WebSocket.OPEN) {
+ wsRef.current.send(JSON.stringify({ type: 'ping' }))
+ }
+ }, [])
+
+ // Connect when project changes
+ useEffect(() => {
+ if (!projectName) {
+ // Disconnect if no project
+ if (wsRef.current) {
+ wsRef.current.close()
+ wsRef.current = null
+ }
+ return
+ }
+
+ connect()
+
+ // Ping every 30 seconds
+ const pingInterval = setInterval(sendPing, 30000)
+
+ return () => {
+ clearInterval(pingInterval)
+ if (reconnectTimeoutRef.current) {
+ clearTimeout(reconnectTimeoutRef.current)
+ }
+ if (wsRef.current) {
+ wsRef.current.close()
+ wsRef.current = null
+ }
+ }
+ }, [projectName, connect, sendPing])
+
+ // Clear logs function
+ const clearLogs = useCallback(() => {
+ setState(prev => ({ ...prev, logs: [] }))
+ }, [])
+
+ return {
+ ...state,
+ clearLogs,
+ }
+}
diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts
new file mode 100644
index 0000000..a420352
--- /dev/null
+++ b/ui/src/lib/api.ts
@@ -0,0 +1,148 @@
+/**
+ * API Client for the Autonomous Coding UI
+ */
+
+import type {
+ ProjectSummary,
+ ProjectDetail,
+ ProjectPrompts,
+ FeatureListResponse,
+ Feature,
+ FeatureCreate,
+ AgentStatusResponse,
+ AgentActionResponse,
+ SetupStatus,
+} from './types'
+
+const API_BASE = '/api'
+
+async function fetchJSON(url: string, options?: RequestInit): Promise {
+ const response = await fetch(`${API_BASE}${url}`, {
+ ...options,
+ headers: {
+ 'Content-Type': 'application/json',
+ ...options?.headers,
+ },
+ })
+
+ if (!response.ok) {
+ const error = await response.json().catch(() => ({ detail: 'Unknown error' }))
+ throw new Error(error.detail || `HTTP ${response.status}`)
+ }
+
+ return response.json()
+}
+
+// ============================================================================
+// Projects API
+// ============================================================================
+
+export async function listProjects(): Promise {
+ return fetchJSON('/projects')
+}
+
+export async function createProject(name: string, specMethod: 'claude' | 'manual' = 'manual'): Promise {
+ return fetchJSON('/projects', {
+ method: 'POST',
+ body: JSON.stringify({ name, spec_method: specMethod }),
+ })
+}
+
+export async function getProject(name: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(name)}`)
+}
+
+export async function deleteProject(name: string): Promise {
+ await fetchJSON(`/projects/${encodeURIComponent(name)}`, {
+ method: 'DELETE',
+ })
+}
+
+export async function getProjectPrompts(name: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(name)}/prompts`)
+}
+
+export async function updateProjectPrompts(
+ name: string,
+ prompts: Partial
+): Promise {
+ await fetchJSON(`/projects/${encodeURIComponent(name)}/prompts`, {
+ method: 'PUT',
+ body: JSON.stringify(prompts),
+ })
+}
+
+// ============================================================================
+// Features API
+// ============================================================================
+
+export async function listFeatures(projectName: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/features`)
+}
+
+export async function createFeature(projectName: string, feature: FeatureCreate): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/features`, {
+ method: 'POST',
+ body: JSON.stringify(feature),
+ })
+}
+
+export async function getFeature(projectName: string, featureId: number): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/features/${featureId}`)
+}
+
+export async function deleteFeature(projectName: string, featureId: number): Promise {
+ await fetchJSON(`/projects/${encodeURIComponent(projectName)}/features/${featureId}`, {
+ method: 'DELETE',
+ })
+}
+
+export async function skipFeature(projectName: string, featureId: number): Promise {
+ await fetchJSON(`/projects/${encodeURIComponent(projectName)}/features/${featureId}/skip`, {
+ method: 'PATCH',
+ })
+}
+
+// ============================================================================
+// Agent API
+// ============================================================================
+
+export async function getAgentStatus(projectName: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/status`)
+}
+
+export async function startAgent(projectName: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/start`, {
+ method: 'POST',
+ })
+}
+
+export async function stopAgent(projectName: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/stop`, {
+ method: 'POST',
+ })
+}
+
+export async function pauseAgent(projectName: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/pause`, {
+ method: 'POST',
+ })
+}
+
+export async function resumeAgent(projectName: string): Promise {
+ return fetchJSON(`/projects/${encodeURIComponent(projectName)}/agent/resume`, {
+ method: 'POST',
+ })
+}
+
+// ============================================================================
+// Setup API
+// ============================================================================
+
+export async function getSetupStatus(): Promise {
+ return fetchJSON('/setup/status')
+}
+
+export async function healthCheck(): Promise<{ status: string }> {
+ return fetchJSON('/health')
+}
diff --git a/ui/src/lib/types.ts b/ui/src/lib/types.ts
new file mode 100644
index 0000000..a5aacec
--- /dev/null
+++ b/ui/src/lib/types.ts
@@ -0,0 +1,112 @@
+/**
+ * TypeScript types for the Autonomous Coding UI
+ */
+
+// Project types
+export interface ProjectStats {
+ passing: number
+ total: number
+ percentage: number
+}
+
+export interface ProjectSummary {
+ name: string
+ has_spec: boolean
+ stats: ProjectStats
+}
+
+export interface ProjectDetail extends ProjectSummary {
+ prompts_dir: string
+}
+
+export interface ProjectPrompts {
+ app_spec: string
+ initializer_prompt: string
+ coding_prompt: string
+}
+
+// Feature types
+export interface Feature {
+ id: number
+ priority: number
+ category: string
+ name: string
+ description: string
+ steps: string[]
+ passes: boolean
+}
+
+export interface FeatureListResponse {
+ pending: Feature[]
+ in_progress: Feature[]
+ done: Feature[]
+}
+
+export interface FeatureCreate {
+ category: string
+ name: string
+ description: string
+ steps: string[]
+ priority?: number
+}
+
+// Agent types
+export type AgentStatus = 'stopped' | 'running' | 'paused' | 'crashed'
+
+export interface AgentStatusResponse {
+ status: AgentStatus
+ pid: number | null
+ started_at: string | null
+}
+
+export interface AgentActionResponse {
+ success: boolean
+ status: AgentStatus
+ message: string
+}
+
+// Setup types
+export interface SetupStatus {
+ claude_cli: boolean
+ credentials: boolean
+ node: boolean
+ npm: boolean
+}
+
+// WebSocket message types
+export type WSMessageType = 'progress' | 'feature_update' | 'log' | 'agent_status' | 'pong'
+
+export interface WSProgressMessage {
+ type: 'progress'
+ passing: number
+ total: number
+ percentage: number
+}
+
+export interface WSFeatureUpdateMessage {
+ type: 'feature_update'
+ feature_id: number
+ passes: boolean
+}
+
+export interface WSLogMessage {
+ type: 'log'
+ line: string
+ timestamp: string
+}
+
+export interface WSAgentStatusMessage {
+ type: 'agent_status'
+ status: AgentStatus
+}
+
+export interface WSPongMessage {
+ type: 'pong'
+}
+
+export type WSMessage =
+ | WSProgressMessage
+ | WSFeatureUpdateMessage
+ | WSLogMessage
+ | WSAgentStatusMessage
+ | WSPongMessage
diff --git a/ui/src/main.tsx b/ui/src/main.tsx
new file mode 100644
index 0000000..e8d9888
--- /dev/null
+++ b/ui/src/main.tsx
@@ -0,0 +1,22 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import App from './App'
+import './styles/globals.css'
+
+const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 5000,
+ refetchOnWindowFocus: false,
+ },
+ },
+})
+
+createRoot(document.getElementById('root')!).render(
+
+
+
+
+ ,
+)
diff --git a/ui/src/styles/globals.css b/ui/src/styles/globals.css
new file mode 100644
index 0000000..78a79bb
--- /dev/null
+++ b/ui/src/styles/globals.css
@@ -0,0 +1,342 @@
+@import "tailwindcss";
+
+/* ============================================================================
+ Neobrutalism Design System
+ ============================================================================ */
+
+@theme {
+ /* Colors */
+ --color-neo-bg: #fffef5;
+ --color-neo-card: #ffffff;
+ --color-neo-pending: #ffd60a;
+ --color-neo-progress: #00b4d8;
+ --color-neo-done: #70e000;
+ --color-neo-accent: #ff006e;
+ --color-neo-danger: #ff5400;
+ --color-neo-border: #1a1a1a;
+ --color-neo-text: #1a1a1a;
+ --color-neo-text-secondary: #4a4a4a;
+
+ /* Fonts */
+ --font-neo-display: 'Space Grotesk', sans-serif;
+ --font-neo-sans: 'DM Sans', sans-serif;
+ --font-neo-mono: 'JetBrains Mono', monospace;
+
+ /* Shadows */
+ --shadow-neo-sm: 2px 2px 0px rgba(0, 0, 0, 1);
+ --shadow-neo-md: 4px 4px 0px rgba(0, 0, 0, 1);
+ --shadow-neo-lg: 6px 6px 0px rgba(0, 0, 0, 1);
+ --shadow-neo-xl: 8px 8px 0px rgba(0, 0, 0, 1);
+
+ /* Transitions */
+ --transition-neo-fast: 0.15s cubic-bezier(0.34, 1.56, 0.64, 1);
+ --transition-neo-normal: 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+/* Base styles */
+body {
+ font-family: var(--font-neo-sans);
+ background-color: var(--color-neo-bg);
+ color: var(--color-neo-text);
+}
+
+/* ============================================================================
+ Component Classes
+ ============================================================================ */
+
+/* Cards */
+.neo-card {
+ background: var(--color-neo-card);
+ border: 3px solid var(--color-neo-border);
+ box-shadow: var(--shadow-neo-md);
+ transition: transform var(--transition-neo-fast), box-shadow var(--transition-neo-fast);
+}
+
+.neo-card:hover {
+ transform: translate(-2px, -2px);
+ box-shadow: var(--shadow-neo-lg);
+}
+
+/* Buttons */
+.neo-btn {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.75rem 1.5rem;
+ font-family: var(--font-neo-display);
+ font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.025em;
+ color: var(--color-neo-text);
+ background: white;
+ border: 3px solid var(--color-neo-border);
+ box-shadow: var(--shadow-neo-md);
+ transition: transform var(--transition-neo-fast), box-shadow var(--transition-neo-fast);
+ cursor: pointer;
+}
+
+.neo-btn:hover {
+ transform: translate(-2px, -2px);
+ box-shadow: var(--shadow-neo-lg);
+}
+
+.neo-btn:active {
+ transform: translate(2px, 2px);
+ box-shadow: var(--shadow-neo-sm);
+}
+
+.neo-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: var(--shadow-neo-sm);
+}
+
+.neo-btn-primary {
+ background: var(--color-neo-accent);
+ color: white;
+}
+
+.neo-btn-success {
+ background: var(--color-neo-done);
+ color: var(--color-neo-text);
+}
+
+.neo-btn-warning {
+ background: var(--color-neo-pending);
+ color: var(--color-neo-text);
+}
+
+.neo-btn-danger {
+ background: var(--color-neo-danger);
+ color: white;
+}
+
+.neo-btn-ghost {
+ background: transparent;
+ color: var(--color-neo-text);
+ box-shadow: none;
+}
+
+.neo-btn-ghost:hover {
+ background: rgba(0, 0, 0, 0.05);
+ color: var(--color-neo-text);
+ box-shadow: none;
+ transform: none;
+}
+
+/* Inputs */
+.neo-input {
+ width: 100%;
+ padding: 0.75rem 1rem;
+ font-family: var(--font-neo-sans);
+ font-size: 1rem;
+ color: var(--color-neo-text);
+ background: white;
+ border: 3px solid var(--color-neo-border);
+ box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.1);
+ transition: transform var(--transition-neo-fast), box-shadow var(--transition-neo-fast);
+}
+
+.neo-input::placeholder {
+ color: var(--color-neo-text-secondary);
+ opacity: 0.7;
+}
+
+.neo-input:focus {
+ outline: none;
+ transform: translate(-1px, -1px);
+ box-shadow: var(--shadow-neo-md);
+ border-color: var(--color-neo-accent);
+}
+
+/* Badge */
+.neo-badge {
+ display: inline-flex;
+ align-items: center;
+ padding: 0.25rem 0.75rem;
+ font-family: var(--font-neo-display);
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ color: var(--color-neo-text);
+ border: 2px solid var(--color-neo-border);
+}
+
+/* Progress Bar */
+.neo-progress {
+ width: 100%;
+ height: 2rem;
+ background: white;
+ border: 3px solid var(--color-neo-border);
+ box-shadow: var(--shadow-neo-sm);
+ overflow: hidden;
+}
+
+.neo-progress-fill {
+ height: 100%;
+ background: var(--color-neo-done);
+ transition: width 0.5s ease-out;
+}
+
+/* Modal */
+.neo-modal-backdrop {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.4);
+ backdrop-filter: blur(2px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 50;
+}
+
+.neo-modal {
+ background: white;
+ border: 4px solid var(--color-neo-border);
+ box-shadow: var(--shadow-neo-xl);
+ animation: popIn 0.3s var(--transition-neo-fast);
+ max-width: 90vw;
+ max-height: 90vh;
+ overflow: auto;
+}
+
+/* Dropdown */
+.neo-dropdown {
+ background: white;
+ border: 3px solid var(--color-neo-border);
+ box-shadow: var(--shadow-neo-lg);
+}
+
+.neo-dropdown-item {
+ padding: 0.75rem 1rem;
+ cursor: pointer;
+ color: var(--color-neo-text);
+ background: transparent;
+ transition: background var(--transition-neo-fast), color var(--transition-neo-fast);
+}
+
+.neo-dropdown-item:hover {
+ background: var(--color-neo-pending);
+ color: var(--color-neo-text);
+}
+
+/* Tooltip */
+.neo-tooltip {
+ background: var(--color-neo-text);
+ color: white;
+ padding: 0.5rem 0.75rem;
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-transform: uppercase;
+ border: 2px solid var(--color-neo-border);
+ box-shadow: var(--shadow-neo-sm);
+}
+
+/* Empty State */
+.neo-empty-state {
+ background: var(--color-neo-bg);
+ border: 4px dashed var(--color-neo-border);
+ padding: 2rem;
+ text-align: center;
+}
+
+/* ============================================================================
+ Animations
+ ============================================================================ */
+
+@keyframes popIn {
+ from {
+ opacity: 0;
+ transform: scale(0.9);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes slideIn {
+ from {
+ opacity: 0;
+ transform: translateX(-20px);
+ }
+ to {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+@keyframes neoPulse {
+ 0%, 100% {
+ box-shadow: 6px 6px 0px rgba(0, 180, 216, 0.3);
+ }
+ 50% {
+ box-shadow: 6px 6px 0px rgba(0, 180, 216, 0.8);
+ }
+}
+
+@keyframes completePop {
+ 0% {
+ transform: scale(0.8);
+ }
+ 60% {
+ transform: scale(1.15);
+ }
+ 100% {
+ transform: scale(1);
+ }
+}
+
+.animate-slide-in {
+ animation: slideIn 0.3s ease-out;
+}
+
+.animate-pop-in {
+ animation: popIn 0.3s var(--transition-neo-fast);
+}
+
+.animate-pulse-neo {
+ animation: neoPulse 2s infinite;
+}
+
+.animate-complete {
+ animation: completePop 0.5s var(--transition-neo-fast);
+}
+
+/* ============================================================================
+ Utilities
+ ============================================================================ */
+
+.font-display {
+ font-family: var(--font-neo-display);
+}
+
+.font-sans {
+ font-family: var(--font-neo-sans);
+}
+
+.font-mono {
+ font-family: var(--font-neo-mono);
+}
+
+/* Scrollbar styling */
+::-webkit-scrollbar {
+ width: 12px;
+ height: 12px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--color-neo-bg);
+ border: 2px solid var(--color-neo-border);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--color-neo-border);
+ border: 2px solid var(--color-neo-border);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--color-neo-text-secondary);
+}
diff --git a/ui/src/vite-env.d.ts b/ui/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/ui/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/ui/tsconfig.json b/ui/tsconfig.json
new file mode 100644
index 0000000..125bb09
--- /dev/null
+++ b/ui/tsconfig.json
@@ -0,0 +1,30 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true,
+
+ /* Paths */
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src"]
+}
diff --git a/ui/tsconfig.node.json b/ui/tsconfig.node.json
new file mode 100644
index 0000000..0d3d714
--- /dev/null
+++ b/ui/tsconfig.node.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2023"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "isolatedModules": true,
+ "moduleDetection": "force",
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/ui/tsconfig.tsbuildinfo b/ui/tsconfig.tsbuildinfo
new file mode 100644
index 0000000..c33c415
--- /dev/null
+++ b/ui/tsconfig.tsbuildinfo
@@ -0,0 +1 @@
+{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/setupwizard.tsx","./src/hooks/useprojects.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"}
\ No newline at end of file
diff --git a/ui/vite.config.ts b/ui/vite.config.ts
new file mode 100644
index 0000000..9e002c7
--- /dev/null
+++ b/ui/vite.config.ts
@@ -0,0 +1,29 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import tailwindcss from '@tailwindcss/vite'
+import path from 'path'
+
+// Backend port - can be overridden via VITE_API_PORT env var
+const apiPort = process.env.VITE_API_PORT || '8000'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+ server: {
+ proxy: {
+ '/api': {
+ target: `http://127.0.0.1:${apiPort}`,
+ changeOrigin: true,
+ },
+ '/ws': {
+ target: `ws://127.0.0.1:${apiPort}`,
+ ws: true,
+ },
+ },
+ },
+})